Re: [PATCH 3/5] alpha/perf: implement ProfileMe sampling

From: Magnus Lindholm

Date: Thu Oct 29 2026 - 11:18:54 EST


Hi Matt,

On Wed, Oct 28, 2026 at 7:09 PM Matt Turner <mattst88@xxxxxxxxx> 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. 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 with the mode bit in regs
> adjusted to match, since 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. PALmode PMPCs are physical
> addresses, so those samples keep the interrupted PC.
>
> 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.
>
> Signed-off-by: Matt Turner <mattst88@xxxxxxxxx>
> ---
> arch/alpha/include/asm/wrperfmon.h | 12 +++
> arch/alpha/kernel/perf_event.c | 157 ++++++++++++++++++++++++++++++++++---
> 2 files changed, 159 insertions(+), 10 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 70840c05d39b..24c96feda942 100644
> --- a/arch/alpha/kernel/perf_event.c
> +++ b/arch/alpha/kernel/perf_event.c
> @@ -30,6 +30,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_PROFILEME 0x1
> +
> /* For tracking PMCs and the hw events they monitor on each CPU. */
> struct cpu_hw_events {
> int enabled;
> @@ -145,6 +152,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.
> @@ -152,9 +172,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)
> @@ -213,9 +245,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,
> };
>
> @@ -276,6 +310,24 @@ 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;
> + 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;
> @@ -320,7 +372,10 @@ static int alpha_perf_event_set_period(struct perf_event *event,
>
> local64_set(&hwc->prev_count, (unsigned long)(-left));
>
> - 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);
>
> @@ -457,7 +512,8 @@ static void maybe_change_configuration(struct cpu_hw_events *cpuc)
> }
>
> if (!(hwc->state & PERF_HES_STOPPED))
> - 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;
> }
> @@ -548,7 +604,7 @@ 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);
> + cpuc->idx_mask &= ~alpha_event_idx_mask(hwc, idx);
> cpuc->n_events--;
> break;
> }
> @@ -573,7 +629,7 @@ 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;
> }
>
> @@ -583,7 +639,7 @@ static void alpha_pmu_stop(struct perf_event *event, int flags)
> }
>
> if (cpuc->enabled)
> - wrperfmon(PERFMON_CMD_DISABLE, (1UL<<hwc->idx));
> + wrperfmon(PERFMON_CMD_DISABLE, alpha_event_idx_mask(hwc, hwc->idx));
> }
>
>
> @@ -602,9 +658,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));
> }
>
>
> @@ -644,6 +700,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;
>
> @@ -657,9 +714,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;
> }
> @@ -668,6 +729,17 @@ 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.
> + */
> + 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
> @@ -832,6 +904,61 @@ void perf_event_print_debug(void)
> }
>
>
> +/*
> + * 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)
> +{
> + struct perf_sample_data data;
> + struct pt_regs pm_regs;
> + unsigned long i_stat, pmpc, ovr;
> +
> + i_stat = wrperfmon(PERFMON_CMD_I_STAT, 0);
> + pmpc = wrperfmon(PERFMON_CMD_PMPC, 0);
> +
> + alpha_perf_event_update(event, hwc, 0, alpha_pmu->pmc_max_period[0]+1);
> +
> + /*
> + * 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, including the user/kernel mode that
> + * perf derives from regs. 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.
> + */
> + pm_regs = *regs;

On Alpha, perf_reg_value(SP) derives kernel SP from regs + 1.
I think passing this copy therefore reports an address past the local copy,
rather than past the original interrupt frame.

> + if (!(pmpc & EV67_PMPC_PAL)) {
> + pm_regs.pc = pmpc & EV67_PMPC_PC_MASK;
> + /* PS bit 3 is the mode bit that user_mode() tests. */
> + if (pm_regs.pc < TASK_SIZE)

If PMPC is in userspace but the interrupt arrived in kernel mode,
this also makes perf_sample_regs_user() select the copied kernel
registers as user registers, bypassing perf_get_regs_user().

The EV67 HRM, section 6.10.3.2 (pages 6-21/6-22), describes PMPC
as the profiled PC; the interrupt follows window closure (also
Architecture Reference Manual, section E.2.5.1, page E-22).
That does not make the interrupt registers the profiled user state.

Could we preserve the original frame for register/stack capture
and supply the profiled IP and mode separately?


> + pm_regs.ps |= 8UL;
> + else
> + pm_regs.ps &= ~8UL;
> + }
> +
> + perf_sample_data_init(&data, 0, hwc->last_period);
> +
> + if (alpha_perf_event_set_period(event, hwc, 0))
> + perf_event_overflow(event, &data, &pm_regs);
> +}
> +
> /*
> * Performance Monitoring Interrupt Service Routine called when a PMC
> * overflows. The PMC that overflowed is passed in la_ptr.
> @@ -866,6 +993,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;
> @@ -888,6 +1019,12 @@ static void alpha_perf_event_irq_handler(unsigned long la_ptr,
> }
>
> hwc = &event->hw;
> + if (hwc->flags & ALPHA_PERF_FLAG_PROFILEME) {
> + alpha_perf_event_profileme(event, hwc, regs);
> + wrperfmon(PERFMON_CMD_ENABLE, cpuc->idx_mask);
> + return;
> + }
> +
> alpha_perf_event_update(event, hwc, idx, alpha_pmu->pmc_max_period[idx]+1);
> perf_sample_data_init(&data, 0, hwc->last_period);
>
>
> --
> 2.55.0
>


I'll test this on my ES40 later.

Thanks

Magnus