Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: James Clark <james.clark@linaro.org>
To: Puranjay Mohan <puranjay12@gmail.com>
Cc: bpf@vger.kernel.org, Alexei Starovoitov <ast@kernel.org>,
	Daniel Borkmann <daniel@iogearbox.net>,
	John Fastabend <john.fastabend@gmail.com>,
	Andrii Nakryiko <andrii@kernel.org>,
	Martin KaFai Lau <martin.lau@linux.dev>,
	Eduard Zingerman <eddyz87@gmail.com>, Song Liu <song@kernel.org>,
	Yonghong Song <yonghong.song@linux.dev>,
	Will Deacon <will@kernel.org>,
	Mark Rutland <mark.rutland@arm.com>,
	Catalin Marinas <catalin.marinas@arm.com>,
	Leo Yan <leo.yan@arm.com>, Rob Herring <robh@kernel.org>,
	Peter Zijlstra <peterz@infradead.org>,
	Ingo Molnar <mingo@redhat.com>,
	Arnaldo Carvalho de Melo <acme@kernel.org>,
	Namhyung Kim <namhyung@kernel.org>,
	Ian Rogers <irogers@google.com>,
	Adrian Hunter <adrian.hunter@intel.com>,
	Shuah Khan <shuah@kernel.org>, Breno Leitao <leitao@debian.org>,
	Ravi Bangoria <ravi.bangoria@amd.com>,
	Stephane Eranian <eranian@google.com>,
	Kumar Kartikeya Dwivedi <memxor@gmail.com>,
	Usama Arif <usama.arif@linux.dev>,
	linux-arm-kernel@lists.infradead.org,
	linux-perf-users@vger.kernel.org,
	linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org,
	kernel-team@meta.com
Subject: Re: [PATCH v5 3/4] perf/arm64: Add BRBE support for bpf_get_branch_snapshot()
Date: Wed, 5 Aug 2026 17:26:53 +0100	[thread overview]
Message-ID: <2b402e4b-b916-4dba-bcb3-634ad58f1710@linaro.org> (raw)
In-Reply-To: <CANk7y0gcj-kRCt-EgL6UK+Tc4xvJQkGz9F=HNpE86kB3fKxymA@mail.gmail.com>



On 05/08/2026 16:44, Puranjay Mohan wrote:
> On Wed, Aug 5, 2026 at 4:14 PM James Clark <james.clark@linaro.org> wrote:
>>
>>
>>
>> On 05/08/2026 15:43, Puranjay Mohan wrote:
>>> On Wed, Aug 5, 2026 at 2:58 PM James Clark <james.clark@linaro.org> wrote:
>>>>
>>>>
>>>>
>>>> On 05/08/2026 12:47, Puranjay Mohan wrote:
>>>>> On Wed, Aug 5, 2026 at 11:07 AM James Clark <james.clark@linaro.org> wrote:
>>>>>>
>>>>>>
>>>>>>
>>>>>> On 03/08/2026 7:54 pm, Puranjay Mohan wrote:
>>>>>>> On Mon, Aug 3, 2026 at 12:07 PM James Clark <james.clark@linaro.org> wrote:
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> On 16/06/2026 16:57, Puranjay Mohan wrote:
>>>>>>>>> Enable bpf_get_branch_snapshot() on ARM64 by implementing the
>>>>>>>>> perf_snapshot_branch_stack static call for BRBE.
>>>>>>>>>
>>>>>>>>> BRBE is paused before masking exceptions to avoid branch buffer
>>>>>>>>> pollution from trace_hardirqs_off(). Exceptions are then masked with
>>>>>>>>> local_daif_save() to prevent PMU overflow pseudo-NMIs from interfering.
>>>>>>>>> If an overflow between pause and DAIF save re-enables BRBE, the snapshot
>>>>>>>>> detects this via BRBFCR_EL1.PAUSED and bails out.
>>>>>>>>>
>>>>>>>>> Branch records are read using perf_entry_from_brbe_regset() with a NULL
>>>>>>>>> event pointer to bypass event-specific filtering. The buffer is
>>>>>>>>> invalidated after reading.
>>>>>>>>>
>>>>>>>>> Introduce a for_each_brbe_entry() iterator to deduplicate bank
>>>>>>>>> iteration between brbe_read_filtered_entries() and the snapshot.
>>>>>>>>>
>>>>>>>>> Signed-off-by: Puranjay Mohan <puranjay@kernel.org>
>>>>>>>>> Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
>>>>>>>>> ---
>>>>>>>>>       drivers/perf/arm_brbe.c  | 128 ++++++++++++++++++++++++++++++++-------
>>>>>>>>>       drivers/perf/arm_brbe.h  |   9 +++
>>>>>>>>>       drivers/perf/arm_pmuv3.c |   5 +-
>>>>>>>>>       3 files changed, 120 insertions(+), 22 deletions(-)
>>>>>>>>>
>>>>>>>>> diff --git a/drivers/perf/arm_brbe.c b/drivers/perf/arm_brbe.c
>>>>>>>>> index effbdeacfcbb..a141ad7abcf2 100644
>>>>>>>>> --- a/drivers/perf/arm_brbe.c
>>>>>>>>> +++ b/drivers/perf/arm_brbe.c
>>>>>>>>> @@ -9,6 +9,7 @@
>>>>>>>>>       #include <linux/types.h>
>>>>>>>>>       #include <linux/bitmap.h>
>>>>>>>>>       #include <linux/perf/arm_pmu.h>
>>>>>>>>> +#include <asm/daifflags.h>
>>>>>>>>>       #include "arm_brbe.h"
>>>>>>>>>
>>>>>>>>>       #define BRBFCR_EL1_BRANCH_FILTERS (BRBFCR_EL1_DIRECT   | \
>>>>>>>>> @@ -256,6 +257,14 @@ static bool valid_brbe_version(int brbe_version)
>>>>>>>>>                  brbe_version == ID_AA64DFR0_EL1_BRBE_BRBE_V1P1;
>>>>>>>>>       }
>>>>>>>>>
>>>>>>>>> +static __always_inline bool cpu_has_brbe(void)
>>>>>>>>
>>>>>>>> This should be more like cpu_valid_brbe_version(). has_brbe() only
>>>>>>>> implies that the CPU has BRBE, not that it's a version that the driver
>>>>>>>> supports. And it's actually just a wrapper around valid_brbe_version()
>>>>>>>> that accesses the ID reg on that CPU, not a functionally different check.
>>>>>>>>
>>>>>>>> But it also looks like valid_brbe_version() isn't called from anywhere
>>>>>>>> else, so why not delete that function and use its name for the new one?
>>>>>>>
>>>>>>> I will do that in next version
>>>>>>>
>>>>>>>>
>>>>>>>>> +{
>>>>>>>>> +     u64 aa64dfr0 = read_sysreg_s(SYS_ID_AA64DFR0_EL1);
>>>>>>>>> +     int brbe = cpuid_feature_extract_unsigned_field(aa64dfr0, ID_AA64DFR0_EL1_BRBE_SHIFT);
>>>>>>>>> +
>>>>>>>>> +     return valid_brbe_version(brbe);
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>>       static void select_brbe_bank(int bank)
>>>>>>>>>       {
>>>>>>>>>           u64 brbfcr;
>>>>>>>>> @@ -271,6 +280,20 @@ static void select_brbe_bank(int bank)
>>>>>>>>>           isb();
>>>>>>>>>       }
>>>>>>>>>
>>>>>>>>> +static inline void __brbe_advance(int *bank, int *idx, int nr_hw)
>>>>>>>>> +{
>>>>>>>>> +     if (++(*idx) >= BRBE_BANK_MAX_ENTRIES &&
>>>>>>>>> +         *bank * BRBE_BANK_MAX_ENTRIES + *idx < nr_hw) {
>>>>>>>>> +             *idx = 0;
>>>>>>>>> +             select_brbe_bank(++(*bank));
>>>>>>>>> +     }
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +#define for_each_brbe_entry(idx, nr_hw)                                      \
>>>>>>>>> +     for (int __bank = (select_brbe_bank(0), 0), idx = 0;            \
>>>>>>>>> +          __bank * BRBE_BANK_MAX_ENTRIES + idx < (nr_hw);            \
>>>>>>>>> +          __brbe_advance(&__bank, &idx, (nr_hw)))
>>>>>>>>> +
>>>>>>>>>       static bool __read_brbe_regset(struct brbe_regset *entry, int idx)
>>>>>>>>>       {
>>>>>>>>>           entry->brbinf = get_brbinf_reg(idx);
>>>>>>>>> @@ -474,11 +497,9 @@ unsigned int brbe_num_branch_records(const struct arm_pmu *armpmu)
>>>>>>>>>
>>>>>>>>>       void brbe_probe(struct arm_pmu *armpmu)
>>>>>>>>>       {
>>>>>>>>> -     u64 brbidr, aa64dfr0 = read_sysreg_s(SYS_ID_AA64DFR0_EL1);
>>>>>>>>> -     u32 brbe;
>>>>>>>>> +     u64 brbidr;
>>>>>>>>>
>>>>>>>>> -     brbe = cpuid_feature_extract_unsigned_field(aa64dfr0, ID_AA64DFR0_EL1_BRBE_SHIFT);
>>>>>>>>> -     if (!valid_brbe_version(brbe))
>>>>>>>>> +     if (!cpu_has_brbe())
>>>>>>>>>                   return;
>>>>>>>>>
>>>>>>>>>           brbidr = read_sysreg_s(SYS_BRBIDR0_EL1);
>>>>>>>>> @@ -618,10 +639,10 @@ static bool perf_entry_from_brbe_regset(int index, struct perf_branch_entry *ent
>>>>>>>>>
>>>>>>>>>           brbe_set_perf_entry_type(entry, brbinf);
>>>>>>>>>
>>>>>>>>> -     if (!branch_sample_no_cycles(event))
>>>>>>>>> +     if (!event || !branch_sample_no_cycles(event))
>>>>>>>>>                   entry->cycles = brbinf_get_cycles(brbinf);
>>>>>>>>>
>>>>>>>>> -     if (!branch_sample_no_flags(event)) {
>>>>>>>>> +     if (!event || !branch_sample_no_flags(event)) {
>>>>>>>>>                   /* Mispredict info is available for source only and complete branch records. */
>>>>>>>>>                   if (!brbe_record_is_target_only(brbinf)) {
>>>>>>>>>                           entry->mispred = brbinf_get_mispredict(brbinf);
>>>>>>>>> @@ -774,32 +795,97 @@ void brbe_read_filtered_entries(struct perf_branch_stack *branch_stack,
>>>>>>>>>       {
>>>>>>>>>           struct arm_pmu *cpu_pmu = to_arm_pmu(event->pmu);
>>>>>>>>>           int nr_hw = brbe_num_branch_records(cpu_pmu);
>>>>>>>>> -     int nr_banks = DIV_ROUND_UP(nr_hw, BRBE_BANK_MAX_ENTRIES);
>>>>>>>>>           int nr_filtered = 0;
>>>>>>>>>           u64 branch_sample_type = event->attr.branch_sample_type;
>>>>>>>>>           DECLARE_BITMAP(event_type_mask, PERF_BR_ARM64_MAX);
>>>>>>>>>
>>>>>>>>>           prepare_event_branch_type_mask(branch_sample_type, event_type_mask);
>>>>>>>>>
>>>>>>>>> -     for (int bank = 0; bank < nr_banks; bank++) {
>>>>>>>>> -             int nr_remaining = nr_hw - (bank * BRBE_BANK_MAX_ENTRIES);
>>>>>>>>> -             int nr_this_bank = min(nr_remaining, BRBE_BANK_MAX_ENTRIES);
>>>>>>>>> +     for_each_brbe_entry(i, nr_hw) {
>>>>>>>>> +             struct perf_branch_entry *pbe = &branch_stack->entries[nr_filtered];
>>>>>>>>>
>>>>>>>>> -             select_brbe_bank(bank);
>>>>>>>>> +             if (!perf_entry_from_brbe_regset(i, pbe, event))
>>>>>>>>> +                     break;
>>>>>>>>>
>>>>>>>>> -             for (int i = 0; i < nr_this_bank; i++) {
>>>>>>>>> -                     struct perf_branch_entry *pbe = &branch_stack->entries[nr_filtered];
>>>>>>>>> +             if (!filter_branch_record(pbe, branch_sample_type, event_type_mask))
>>>>>>>>> +                     continue;
>>>>>>>>>
>>>>>>>>> -                     if (!perf_entry_from_brbe_regset(i, pbe, event))
>>>>>>>>> -                             goto done;
>>>>>>>>> +             nr_filtered++;
>>>>>>>>> +     }
>>>>>>>>>
>>>>>>>>> -                     if (!filter_branch_record(pbe, branch_sample_type, event_type_mask))
>>>>>>>>> -                             continue;
>>>>>>>>> +     branch_stack->nr = nr_filtered;
>>>>>>>>> +}
>>>>>>>>>
>>>>>>>>> -                     nr_filtered++;
>>>>>>>>> -             }
>>>>>>>>> +/*
>>>>>>>>> + * Best-effort BRBE snapshot for BPF tracing. Pause BRBE to avoid
>>>>>>>>> + * self-recording and return 0 if the snapshot state appears disturbed.
>>>>>>>>> + */
>>>>>>>>> +int arm_brbe_snapshot_branch_stack(struct perf_branch_entry *entries, unsigned int cnt)
>>>>>>>>> +{
>>>>>>>>> +     unsigned long flags;
>>>>>>>>> +     int nr_hw, nr_copied = 0;
>>>>>>>>> +     u64 brbfcr, brbcr;
>>>>>>>>> +
>>>>>>>>> +     if (!cnt)
>>>>>>>>> +             return 0;
>>>>>>>>
>>>>>>>> If you're trying to avoid branches before pausing BRBE, can't you check
>>>>>>>> this after the pause?
>>>>>>>
>>>>>>> will drop this in the next version and this is just an optimization.
>>>>>>>
>>>>>>>>
>>>>>>>>> +
>>>>>>>>> +     /* Guard against running on a CPU without BRBE (e.g. big.LITTLE). */
>>>>>>>>> +     if (!cpu_has_brbe())
>>>>>>>>> +             return 0;
>>>>>>>>> +
>>>>>>>>> +     /*
>>>>>>>>> +      * Pause BRBE first to avoid recording our own branches. The
>>>>>>>>> +      * sysreg read/write and ISB are branchless, so pausing before
>>>>>>>>> +      * checking BRBCR avoids polluting the buffer with our own
>>>>>>>>> +      * conditional branches.
>>>>>>>>> +      */
>>>>>>>>> +     brbfcr = read_sysreg_s(SYS_BRBFCR_EL1);
>>>>>>>>> +     brbcr = read_sysreg_s(SYS_BRBCR_EL1);
>>>>>>>>> +     write_sysreg_s(brbfcr | BRBFCR_EL1_PAUSED, SYS_BRBFCR_EL1);
>>>>>>>>
>>>>>>>> Can this work without first disabling interrupts? Sashiko pointed it
>>>>>>>> out, but I think it's correct. If you read an active state into brbfcr,
>>>>>>>> then the PMU event fires and disables BRBE, then you disable interrupts,
>>>>>>>> then you would restore an active state when it should be inactive.
>>>>>>>> Surely the only way to do it properly is to disable interrupts before
>>>>>>>> touching anything at all, if the PMU handler is also touching the same
>>>>>>>> registers?
>>>>>>>>
>>>>>>>> If you want to avoid trace_hardirqs_off() can you make a new
>>>>>>>> raw_local_daif_save() that disables interrupts and then call
>>>>>>>> trace_hardirqs_off() yourself after pausing BRBE? Or not call
>>>>>>>> trace_hardirqs_off() at all? There is a comment mentioning something
>>>>>>>> like that in arch/arm64/kernel/suspend.c.
>>>>>>>
>>>>>>> Yes. I'll add raw_local_daif_save()/raw_local_daif_restore() and mask before
>>>>>>> touching any BRBE register, which fixes the stale restore. trace_hardirqs_off()
>>>>>>> then moves below the pause so lockdep still sees a balanced off/on pair while
>>>>>>> its branches land in an already paused buffer.
>>>>>>>
>>>>>>>>
>>>>>>>> Also, disabling interrupts doesn't stop the PMU event from overflowing
>>>>>>>> and changing the state of PAUSED either. I think this is another path
>>>>>>>> that leads to you restoring the wrong state, so don't you also need to
>>>>>>>> disable the PMU?
>>>>>>>
>>>>>>> Hardware only sets PAUSED on a BRBE freeze event (RBHYTD), and a freeze needs
>>>>>>> BRBE to not already be paused (RNXCWF). So once we have paused, nothing changes
>>>>>>> underneath us and the PMU does not need disabling. It would not help anyway:
>>>>>>> armv8pmu_stop() calls brbe_disable(), which zeroes BRBCR_EL1 and discards the
>>>>>>> records we came to read.
>>>>>>
>>>>>> That does mean you throw away real freeze events while paused though, in
>>>>>> addition to the brbe_invalidate() you need to avoid non contiguous
>>>>>> buffers. So it takes branches away from PMU events.
>>>>>
>>>>> RBHYTD gives a freeze two effects: PAUSED is set, and BRBTS_EL1 captures a
>>>>> timestamp. Recording has already stopped because we paused, and the driver never
>>>>> reads BRBTS_EL1. On the way out we check PMOVSCLR_EL0 and leave PAUSED set if a
>>>>> counter overflowed, so a pending overflow handler still finds a frozen buffer.
>>>>>
>>>>>> So it takes branches away from PMU events.
>>>>>
>>>>> Yes, but that is brbe_invalidate(), not the pause. Interrupts are masked and
>>>>> nothing else runs on the CPU, so the only branches the pause suppresses are the
>>>>> snapshot's own.
>>>>>
>>>>>>>
>>>>>>> You are right that a freeze can still land in the window between reading BRBFCR
>>>>>>> and setting PAUSED, and restoring the value we read would then clear a PAUSED
>>>>>>> bit the hardware set. So v6 checks PMOVSCLR_EL0 and leaves BRBE paused if a
>>>>>>> counter has overflowed. Reads of PMOVSCLR are non-destructive and it stays set
>>>>>>> until the overflow handler clears it, so it is still visible after we have set
>>>>>>> PAUSED ourselves:
>>>>>>>
>>>>>>>             if (!valid_brbe_version())
>>>>>>>                     return 0;
>>>>>>>
>>>>>>>             flags = raw_local_daif_save();
>>>>>>>
>>>>>>>             brbfcr = read_sysreg_s(SYS_BRBFCR_EL1);
>>>>>>>             brbcr = read_sysreg_s(SYS_BRBCR_EL1);
>>>>>>>
>>>>>>>             write_sysreg_s(brbfcr | BRBFCR_EL1_PAUSED, SYS_BRBFCR_EL1);
>>>>>>>             isb();
>>>>>>>
>>>>>>>             trace_hardirqs_off();
>>>>>>>
>>>>>>>             /* BRBCR_EL1 is zero while the driver has BRBE disabled. */
>>>>>>>             if (!brbcr)
>>>>>>>                     goto restore;
>>>>>>>
>>>>>>>             ... read the records ...
>>>>>>>
>>>>>>
>>>>>> Up to the point where you read the records you technically don't need
>>>>>> any branches (if you don't do trace_hardirqs_off()) so you could do the
>>>>>> whole thing without pausing. Just disable interrupts, read every branch
>>>>>> entry unconditionally, re-enable interrupts and then find the last valid
>>>>>> entry and do the branchy stuff after reading.
>>>>>>
>>>>>> I'm thinking out loud, but doesn't that make it a lot easier? And it
>>>>>> avoids the brbe_invalidate() which takes the branches away from the PMU
>>>>>> event. It also avoids having to think too hard about racing with PMU
>>>>>> events causing a freeze even after interrupts are disabled and after
>>>>>> reading the freeze value:
>>>>>>
>>>>>>       raw_local_daif_save();
>>>>>>       brbfcr = read_sysreg_s(SYS_BRBFCR_EL1);
>>>>>>       brbcr = read_sysreg_s(SYS_BRBCR_EL1);
>>>>>>       select_bank(0);
>>>>>>       read_record(0);
>>>>>>       read_record(1);
>>>>>>       ...
>>>>>>       select_bank(0);
>>>>>>       read_record(0);
>>>>>>       read_record(1);
>>>>>>       ...
>>>>>>       write_sysreg_s(brbfcr, SYS_BRBFCR_EL1);
>>>>>>       isb();
>>>>>>       local_daif_restore();
>>>>>>
>>>>>>       /* Now do post processing, find last valid record, check if it was
>>>>>> enabled by looking at brbcr etc. */
>>>>>
>>>>> MRS is not a branch, so that works, but three things would have to change:
>>>>>
>>>>> 1. perf_entry_from_brbe_regset() goes through BRBE_REGN_SWITCH, a 32 case
>>>>>       switch, because the register number has to be an immediate. gcc emits a jump
>>>>>       table and the function has 52 branches in the object file. The read would
>>>>>       need full unrolling.
>>>>>
>>>>
>>>> Yes you would have to manually unroll it. I doubt the compiler would
>>>> emit a branch for BRBE_REGN_SWITCH() because your indexes are static if
>>>> it's unrolled. But if it does you can change it to a sequence of
>>>> read_sysreg_s()s.
>>>>
>>>> If you really want to be sure there are no branches, write it in a
>>>> single asm block.
>>>
>>> I will try that approach in the next version.
>>>
>>>>> 2. RPGDLX needs an ISB before the reads, and Table D19-10 makes it
>>>>>       IMPLEMENTATION DEFINED whether ISB itself generates a record. When we pause,
>>>>>       IZCHRF means the pausing ISB cannot pollute the buffer it is about to read.
>>>>>
>>>>
>>>> I assume one potential extra record from the isb() is acceptible seeing
>>>> as you already have two or more conditions plus a function call before
>>>> the pause? Is the problem that you don't know whether to filter it out
>>>> later because it's IMPDEF and isn't always there? You already don't know
>>>> exactly how many branches there will be before the pause beause it's
>>>> written in C. So I'm not sure what the exact issue here is.
>>>>
>>>>> 3. 64 record parts still need a bank switch, so BRBFCR_EL1 still has to be
>>>>>       written and restored.
>>>>>
>>>>
>>>> Yes that was included in my pseudo code example but it's still
>>>> branchless. I did miss that you might need an isb() after disabling
>>>> interrupts, but you added one for RPGDLX anyway.
>>>>
>>>>> Correctness would then depend on the read staying branchless, which is not
>>>>> checkable at build time and fails silently: a branch mid read shifts the buffer,
>>>>
>>>> Why would the compiler insert a branch between two read_sysreg_s()s,
>>>> which are asm volatile? Is that allowed?
>>>
>>> You are right, I just over complicated it!
>>>
>>>>> giving a duplicate and a gap. 64 * 24 bytes of records also wants a per-CPU
>>>>> scratch buffer rather than the stack.
>>>>>
>>>>
>>>> A per-CPU scratch buffer doesn't sound too bad. But aren't you in
>>>> control of how many entries are available to write to? You can reject
>>>> any calls that have fewer than 64 and always write directly to *entries.
>>>>
>>>> You could also compare with 'cnt' after reading each record and exit the
>>>> read section. Like you say below, branches not taken don't generate
>>>> records, and once the branch is taken you stop reading so after that
>>>> point generating records doesn't matter.
>>>>
>>>>> Happy to prototype it. What I would not do is pause without invalidating:
>>>>> records are from/to pairs, so a consumer walking across the hole reconstructs a
>>>>> call path that never happened. The invalidate was Mark's request after the RFC,
>>>>> "to maintain record contiguity for other consumers", so dropping the pause drops
>>>>> that too.
>>>>
>>>> Well the point was to not have to pause at all, so there's no need to
>>>> invalidate either. Even if you did add a pause, as long as there are no
>>>> branches between the pause and resume you don't need an invalidate
>>>> because you didn't miss any branches.
>>>>
>>>>>
>>>>> I was thinking of this for v6:
>>>>>
>>>>>            flags = raw_local_daif_save();
>>>>>
>>>>>            /* The BRBE sysregs below are UNDEFINED without this. */
>>>>>            if (!valid_brbe_version()) {
>>>>
>>>> You don't need to disable interrupts to call this, it's a constant. Or
>>>> is it to stop migration?
>>>
>>> It was to stop migration.
>>>
>>
>> Ah ok, so V5 wasn't correct then.
> 
> Yes, It was buggy! I missed that one CPU could implement BRBE while
> another doesn't
> 
>>
>> Separately to this, I'm also not sure how this BPF call is invoked. Is
>> it supposed to target a CPU, or an event or a process? How do you know
>> you are getting the branches from the CPU that you want?
> 
> So, A BPF program can attach to a function's entry through ftrace or
> kprobe and from there we call this helper to find out what branches
> were taken to reach that function.
> A BPF program runs on a CPU with migration always disabled, so it

In that case you can check for BRBE support before disabling interrupts.

So I think V5 wasn't buggy and you can carry on doing 
valid_brbe_version() at the same place in V6.

> knows which cpu it is getting the branches from. And It can also
> filter for a process as it knows which process is called the BPF
> program, let's say a process does a syscall and we attach a bpf
> program to it as the example I gave with the retsnoop tool in the
> cover letter's Usage model section. A BPF program can also attach to a
> tracepoint, or even to a perf event and this BPF helper can be called
> from any of those contexts, but the filtering based on cpu or pid or
> anything else is done by the BPF program itself, this helper should
> just return the raw branch records.
> 

Thanks for the explanation.

>>>>
>>>> V5 reads sysregs before disabling interrupts so I assume migration isn't
>>>> an issue here.
>>>>
>>>>>                    raw_local_daif_restore(flags);
>>>>>                    return 0;
>>>>>            }
>>>>>
>>>>>            brbfcr = read_sysreg_s(SYS_BRBFCR_EL1);
>>>>>            brbcr = read_sysreg_s(SYS_BRBCR_EL1);
>>>>>
>>>>>            write_sysreg_s(brbfcr | BRBFCR_EL1_PAUSED, SYS_BRBFCR_EL1);
>>>>>            isb();
>>>>>
>>>>>            /* BRBCR_EL1 is zero while the driver has BRBE disabled. */
>>>>>            if (!brbcr) {
>>>>>                    write_sysreg_s(brbfcr, SYS_BRBFCR_EL1);
>>>>>                    isb();
>>>>>                    raw_local_daif_restore(flags);
>>>>>                    return 0;
>>>>>            }
>>>>>
>>>>>            trace_hardirqs_off();
>>>>>
>>>>>            ... read the records ...
>>>>>
>>>>>            if (!(brbfcr & BRBFCR_EL1_PAUSED) &&
>>>>>                !(read_pmovsclr() & (ARMV8_PMU_OVSR_P | ARMV8_PMU_OVSR_F)))
>>>>>                    brbe_invalidate();
>>>>>            else
>>>>>                    brbfcr |= BRBFCR_EL1_PAUSED;
>>>>>
>>>>>            write_sysreg_s(brbfcr, SYS_BRBFCR_EL1);
>>>>>            isb();
>>>>>            local_daif_restore(flags);
>>>>>
>>>>> Exceptions are masked before anything is sampled, BRBCR_EL1 is only read and
>>>>> never written, and BRBFCR_EL1 is written back from the value saved under the
>>>>> mask. cpu_has_brbe() is now valid_brbe_version().
>>>>>
>>>>> Neither conditional costs a record: valid_brbe_version() falls through when BRBE
>>>>> is present (RBBNSZ, only taken branches are recorded), and the BRBCR_EL1 test is
>>>>
>>>> Isn't the compiler free to invert it and make the happy path a taken
>>>> branch. I don't think any of that can be assumed without writing in
>>>> assembly.
>>>>
>>>>> after the pause. It cannot move later because BRBFCR_EL1 is UNDEFINED without
>>>>> FEAT_BRBE.
>>>>>
>>>>> PMOVSCLR_EL0 is masked because PMCCNTR_EL0 is bit 31 and PMCR_EL0.N is at most
>>>>> 31, so the cycle counter is outside every range RNXCWF, RGXGWY, RPKTXQ and
>>>>> RLDMVK name. Unmasked, a cycle counter overflow looked like a freeze.
>>>>
>>>> Sorry I didn't understand this bit. Can you elaborate?
>>>>
>>>>>
>>>>> Do you think this version works?
>>>>>
>>>>> Thanks,
>>>>> Puranjay
>>>>
>>>>
>>>>    From your previous reply:
>>>>
>>>>      "Hardware only sets PAUSED on a BRBE freeze event (RBHYTD), and a
>>>>       freeze needs BRBE to not already be paused (RNXCWF). So once we have
>>>>       paused, nothing changes underneath us and the PMU does not need
>>>>       disabling."
>>>>
>>>> I'm still not 100% convinced this is correct. You read BRBFCR_EL1 and
>>>> write PAUSED to it in separate instructions. The PMU can overflow and
>>>> change the PAUSED state between those two instructions leading to
>>>> restoration of the wrong value.
>>>
>>> I was checking if the overflow happened and leaving it paused in that case.
>>>
>>
>> But couldn't it overflow after that check? I still think it's vulnerable
>> to the same issue unless you disable the PMU completely.
> 
> yeah you are right, I missed that it can overflow after the check.
> 
>>
>>>> Isn't this non pausing version way simpler to understand, and also has
>>>> the benefit of not invalidating someone elses BRBE buffers. The only
>>>> downside seems to be that it might have some assembly to make sure the
>>>> if statements are always not taken branches rather than inverted, but
>>>> personally I don't think that makes it any harder to understand than the
>>>> branchy pausing version in V5:
>>>>
>>>>      #define read_record(i)
>>>>        if (i >= cnt)                                       \
>>>>           goto out;                                        \
>>>>        isb(); /* Ensure our own exit branch isn't read? */ \
>>>>        entries[i].inf = read_brbe_inf(i)                   \
>>>>        if (!inf)                                           \
>>>>           goto out;                                        \
>>>>        entries[i].src = read_brb_src(i)                    \
>>>>        entries[i].dst = read_brb_dst(i)                    \
>>>>
>>>>      raw_local_daif_save();
>>>>
>>>>      /* disable counters to stop BRBFCR_EL1.PAUSE state changing */
>>>>      pmcr = armv8pmu_pmcr_read();
>>>>      armv8pmu_pmcr_write(pmcr & ~ARMV8_PMU_PMCR_E);
>>>>      isb();
>>>>
>>>>      brbfcr = read_sysreg_s(SYS_BRBFCR_EL1);
>>>>      brbcr = read_sysreg_s(SYS_BRBCR_EL1);
>>>>
>>>>      select_bank(0);
>>>>      read_record(0);
>>>>      read_record(1);
>>>>      ...
>>>>      select_bank(1);
>>>>      read_record(32);
>>>>      read_record(33);
>>>>      ...
>>>>
>>>> out:
>>>>      write_sysreg_s(brbfcr, SYS_BRBFCR_EL1);
>>>>      isb();
>>>>      armv8pmu_pmcr_write(pmcr);
>>>>      raw_local_daif_restore();
>>>>
>>>>      /* Post process entries[n].inf etc into correct format */
>>>
>>> I will try out this approach and get back.
>>>
>>> Thanks,
>>> Puranjay
>>
>> Nice, hope it can work.
>>



  reply	other threads:[~2026-08-05 16:27 UTC|newest]

Thread overview: 26+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-16 15:57 [PATCH v5 0/4] arm64: Add BRBE support for bpf_get_branch_snapshot() Puranjay Mohan
2026-06-16 15:57 ` [PATCH v5 1/4] perf/core: Fix sched_task callbacks for CPU-wide branch stack events Puranjay Mohan
2026-07-23 12:14   ` Usama Arif
2026-07-30 19:56   ` Andrii Nakryiko
2026-06-16 15:57 ` [PATCH v5 2/4] perf/core: Clear the whole branch entry in perf_clear_branch_entry() Puranjay Mohan
2026-07-07 14:24   ` Usama Arif
2026-07-30 19:56   ` Andrii Nakryiko
2026-08-03 10:00   ` James Clark
2026-06-16 15:57 ` [PATCH v5 3/4] perf/arm64: Add BRBE support for bpf_get_branch_snapshot() Puranjay Mohan
2026-08-03 11:07   ` James Clark
2026-08-03 18:54     ` Puranjay Mohan
2026-08-05 10:07       ` James Clark
2026-08-05 11:47         ` Puranjay Mohan
2026-08-05 13:58           ` James Clark
2026-08-05 14:43             ` Puranjay Mohan
2026-08-05 15:14               ` James Clark
2026-08-05 15:44                 ` Puranjay Mohan
2026-08-05 16:26                   ` James Clark [this message]
2026-08-05 20:46                     ` Puranjay Mohan
2026-08-06 12:46                       ` Puranjay Mohan
2026-08-06 13:03                         ` James Clark
2026-08-06 12:53                       ` James Clark
2026-06-16 15:57 ` [PATCH v5 4/4] selftests/bpf: Adjust wasted entries threshold for ARM64 BRBE Puranjay Mohan
2026-07-15 13:24 ` [PATCH v5 0/4] arm64: Add BRBE support for bpf_get_branch_snapshot() Puranjay Mohan
2026-07-23 11:38   ` Puranjay Mohan
2026-07-30 19:55     ` Andrii Nakryiko

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=2b402e4b-b916-4dba-bcb3-634ad58f1710@linaro.org \
    --to=james.clark@linaro.org \
    --cc=acme@kernel.org \
    --cc=adrian.hunter@intel.com \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=catalin.marinas@arm.com \
    --cc=daniel@iogearbox.net \
    --cc=eddyz87@gmail.com \
    --cc=eranian@google.com \
    --cc=irogers@google.com \
    --cc=john.fastabend@gmail.com \
    --cc=kernel-team@meta.com \
    --cc=leitao@debian.org \
    --cc=leo.yan@arm.com \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=linux-perf-users@vger.kernel.org \
    --cc=mark.rutland@arm.com \
    --cc=martin.lau@linux.dev \
    --cc=memxor@gmail.com \
    --cc=mingo@redhat.com \
    --cc=namhyung@kernel.org \
    --cc=peterz@infradead.org \
    --cc=puranjay12@gmail.com \
    --cc=ravi.bangoria@amd.com \
    --cc=robh@kernel.org \
    --cc=shuah@kernel.org \
    --cc=song@kernel.org \
    --cc=usama.arif@linux.dev \
    --cc=will@kernel.org \
    --cc=yonghong.song@linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox