--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Branch Record Buffer Extension Driver.
+ *
+ * Copyright (C) 2022-2025 ARM Limited
+ *
+ * Author: Anshuman Khandual <anshuman.khandual@arm.com>
+ */
+#include <linux/types.h>
+#include <linux/bitmap.h>
+#include <linux/perf/arm_pmu.h>
+#include "arm_brbe.h"
+
+#define BRBFCR_EL1_BRANCH_FILTERS (BRBFCR_EL1_DIRECT   | \
+                                  BRBFCR_EL1_INDIRECT | \
+                                  BRBFCR_EL1_RTN      | \
+                                  BRBFCR_EL1_INDCALL  | \
+                                  BRBFCR_EL1_DIRCALL  | \
+                                  BRBFCR_EL1_CONDDIR)
+
+/*
+ * BRBTS_EL1 is currently not used for branch stack implementation
+ * purpose but BRBCR_ELx.TS needs to have a valid value from all
+ * available options. BRBCR_ELx_TS_VIRTUAL is selected for this.
+ */
+#define BRBCR_ELx_DEFAULT_TS      FIELD_PREP(BRBCR_ELx_TS_MASK, BRBCR_ELx_TS_VIRTUAL)
+
+/*
+ * BRBE Buffer Organization
+ *
+ * BRBE buffer is arranged as multiple banks of 32 branch record
+ * entries each. An individual branch record in a given bank could
+ * be accessed, after selecting the bank in BRBFCR_EL1.BANK and
+ * accessing the registers i.e [BRBSRC, BRBTGT, BRBINF] set with
+ * indices [0..31].
+ *
+ * Bank 0
+ *
+ *     ---------------------------------       ------
+ *     | 00 | BRBSRC | BRBTGT | BRBINF |       | 00 |
+ *     ---------------------------------       ------
+ *     | 01 | BRBSRC | BRBTGT | BRBINF |       | 01 |
+ *     ---------------------------------       ------
+ *     | .. | BRBSRC | BRBTGT | BRBINF |       | .. |
+ *     ---------------------------------       ------
+ *     | 31 | BRBSRC | BRBTGT | BRBINF |       | 31 |
+ *     ---------------------------------       ------
+ *
+ * Bank 1
+ *
+ *     ---------------------------------       ------
+ *     | 32 | BRBSRC | BRBTGT | BRBINF |       | 00 |
+ *     ---------------------------------       ------
+ *     | 33 | BRBSRC | BRBTGT | BRBINF |       | 01 |
+ *     ---------------------------------       ------
+ *     | .. | BRBSRC | BRBTGT | BRBINF |       | .. |
+ *     ---------------------------------       ------
+ *     | 63 | BRBSRC | BRBTGT | BRBINF |       | 31 |
+ *     ---------------------------------       ------
+ */
+#define BRBE_BANK_MAX_ENTRIES  32
+
+struct brbe_regset {
+       u64 brbsrc;
+       u64 brbtgt;
+       u64 brbinf;
+};
+
+#define PERF_BR_ARM64_MAX (PERF_BR_MAX + PERF_BR_NEW_MAX)
+
+struct brbe_hw_attr {
+       int     brbe_version;
+       int     brbe_cc;
+       int     brbe_nr;
+       int     brbe_format;
+};
+
+#define BRBE_REGN_CASE(n, case_macro) \
+       case n: case_macro(n); break
+
+#define BRBE_REGN_SWITCH(x, case_macro)                                \
+       do {                                                    \
+               switch (x) {                                    \
+               BRBE_REGN_CASE(0, case_macro);                  \
+               BRBE_REGN_CASE(1, case_macro);                  \
+               BRBE_REGN_CASE(2, case_macro);                  \
+               BRBE_REGN_CASE(3, case_macro);                  \
+               BRBE_REGN_CASE(4, case_macro);                  \
+               BRBE_REGN_CASE(5, case_macro);                  \
+               BRBE_REGN_CASE(6, case_macro);                  \
+               BRBE_REGN_CASE(7, case_macro);                  \
+               BRBE_REGN_CASE(8, case_macro);                  \
+               BRBE_REGN_CASE(9, case_macro);                  \
+               BRBE_REGN_CASE(10, case_macro);                 \
+               BRBE_REGN_CASE(11, case_macro);                 \
+               BRBE_REGN_CASE(12, case_macro);                 \
+               BRBE_REGN_CASE(13, case_macro);                 \
+               BRBE_REGN_CASE(14, case_macro);                 \
+               BRBE_REGN_CASE(15, case_macro);                 \
+               BRBE_REGN_CASE(16, case_macro);                 \
+               BRBE_REGN_CASE(17, case_macro);                 \
+               BRBE_REGN_CASE(18, case_macro);                 \
+               BRBE_REGN_CASE(19, case_macro);                 \
+               BRBE_REGN_CASE(20, case_macro);                 \
+               BRBE_REGN_CASE(21, case_macro);                 \
+               BRBE_REGN_CASE(22, case_macro);                 \
+               BRBE_REGN_CASE(23, case_macro);                 \
+               BRBE_REGN_CASE(24, case_macro);                 \
+               BRBE_REGN_CASE(25, case_macro);                 \
+               BRBE_REGN_CASE(26, case_macro);                 \
+               BRBE_REGN_CASE(27, case_macro);                 \
+               BRBE_REGN_CASE(28, case_macro);                 \
+               BRBE_REGN_CASE(29, case_macro);                 \
+               BRBE_REGN_CASE(30, case_macro);                 \
+               BRBE_REGN_CASE(31, case_macro);                 \
+               default: WARN(1, "Invalid BRB* index %d\n", x); \
+               }                                               \
+       } while (0)
+
+#define RETURN_READ_BRBSRCN(n) \
+       return read_sysreg_s(SYS_BRBSRC_EL1(n))
+static inline u64 get_brbsrc_reg(int idx)
+{
+       BRBE_REGN_SWITCH(idx, RETURN_READ_BRBSRCN);
+       return 0;
+}
+
+#define RETURN_READ_BRBTGTN(n) \
+       return read_sysreg_s(SYS_BRBTGT_EL1(n))
+static u64 get_brbtgt_reg(int idx)
+{
+       BRBE_REGN_SWITCH(idx, RETURN_READ_BRBTGTN);
+       return 0;
+}
+
+#define RETURN_READ_BRBINFN(n) \
+       return read_sysreg_s(SYS_BRBINF_EL1(n))
+static u64 get_brbinf_reg(int idx)
+{
+       BRBE_REGN_SWITCH(idx, RETURN_READ_BRBINFN);
+       return 0;
+}
+
+static u64 brbe_record_valid(u64 brbinf)
+{
+       return FIELD_GET(BRBINFx_EL1_VALID_MASK, brbinf);
+}
+
+static bool brbe_invalid(u64 brbinf)
+{
+       return brbe_record_valid(brbinf) == BRBINFx_EL1_VALID_NONE;
+}
+
+static bool brbe_record_is_complete(u64 brbinf)
+{
+       return brbe_record_valid(brbinf) == BRBINFx_EL1_VALID_FULL;
+}
+
+static bool brbe_record_is_source_only(u64 brbinf)
+{
+       return brbe_record_valid(brbinf) == BRBINFx_EL1_VALID_SOURCE;
+}
+
+static bool brbe_record_is_target_only(u64 brbinf)
+{
+       return brbe_record_valid(brbinf) == BRBINFx_EL1_VALID_TARGET;
+}
+
+static int brbinf_get_in_tx(u64 brbinf)
+{
+       return FIELD_GET(BRBINFx_EL1_T_MASK, brbinf);
+}
+
+static int brbinf_get_mispredict(u64 brbinf)
+{
+       return FIELD_GET(BRBINFx_EL1_MPRED_MASK, brbinf);
+}
+
+static int brbinf_get_lastfailed(u64 brbinf)
+{
+       return FIELD_GET(BRBINFx_EL1_LASTFAILED_MASK, brbinf);
+}
+
+static u16 brbinf_get_cycles(u64 brbinf)
+{
+       u32 exp, mant, cycles;
+       /*
+        * Captured cycle count is unknown and hence
+        * should not be passed on to userspace.
+        */
+       if (brbinf & BRBINFx_EL1_CCU)
+               return 0;
+
+       exp = FIELD_GET(BRBINFx_EL1_CC_EXP_MASK, brbinf);
+       mant = FIELD_GET(BRBINFx_EL1_CC_MANT_MASK, brbinf);
+
+       if (!exp)
+               return mant;
+
+       cycles = (mant | 0x100) << (exp - 1);
+
+       return min(cycles, U16_MAX);
+}
+
+static int brbinf_get_type(u64 brbinf)
+{
+       return FIELD_GET(BRBINFx_EL1_TYPE_MASK, brbinf);
+}
+
+static int brbinf_get_el(u64 brbinf)
+{
+       return FIELD_GET(BRBINFx_EL1_EL_MASK, brbinf);
+}
+
+void brbe_invalidate(void)
+{
+       /* Ensure all branches before this point are recorded */
+       isb();
+       asm volatile(BRB_IALL_INSN);
+       /* Ensure all branch records are invalidated after this point */
+       isb();
+}
+
+static bool valid_brbe_nr(int brbe_nr)
+{
+       return brbe_nr == BRBIDR0_EL1_NUMREC_8 ||
+              brbe_nr == BRBIDR0_EL1_NUMREC_16 ||
+              brbe_nr == BRBIDR0_EL1_NUMREC_32 ||
+              brbe_nr == BRBIDR0_EL1_NUMREC_64;
+}
+
+static bool valid_brbe_cc(int brbe_cc)
+{
+       return brbe_cc == BRBIDR0_EL1_CC_20_BIT;
+}
+
+static bool valid_brbe_format(int brbe_format)
+{
+       return brbe_format == BRBIDR0_EL1_FORMAT_FORMAT_0;
+}
+
+static bool valid_brbidr(u64 brbidr)
+{
+       int brbe_format, brbe_cc, brbe_nr;
+
+       brbe_format = FIELD_GET(BRBIDR0_EL1_FORMAT_MASK, brbidr);
+       brbe_cc = FIELD_GET(BRBIDR0_EL1_CC_MASK, brbidr);
+       brbe_nr = FIELD_GET(BRBIDR0_EL1_NUMREC_MASK, brbidr);
+
+       return valid_brbe_format(brbe_format) && valid_brbe_cc(brbe_cc) && valid_brbe_nr(brbe_nr);
+}
+
+static bool valid_brbe_version(int brbe_version)
+{
+       return brbe_version == ID_AA64DFR0_EL1_BRBE_IMP ||
+              brbe_version == ID_AA64DFR0_EL1_BRBE_BRBE_V1P1;
+}
+
+static void select_brbe_bank(int bank)
+{
+       u64 brbfcr;
+
+       brbfcr = read_sysreg_s(SYS_BRBFCR_EL1);
+       brbfcr &= ~BRBFCR_EL1_BANK_MASK;
+       brbfcr |= SYS_FIELD_PREP(BRBFCR_EL1, BANK, bank);
+       write_sysreg_s(brbfcr, SYS_BRBFCR_EL1);
+       /*
+        * Arm ARM (DDI 0487K.a) D.18.4 rule PPBZP requires explicit sync
+        * between setting BANK and accessing branch records.
+        */
+       isb();
+}
+
+static bool __read_brbe_regset(struct brbe_regset *entry, int idx)
+{
+       entry->brbinf = get_brbinf_reg(idx);
+
+       if (brbe_invalid(entry->brbinf))
+               return false;
+
+       entry->brbsrc = get_brbsrc_reg(idx);
+       entry->brbtgt = get_brbtgt_reg(idx);
+       return true;
+}
+
+/*
+ * Generic perf branch filters supported on BRBE
+ *
+ * New branch filters need to be evaluated whether they could be supported on
+ * BRBE. This ensures that such branch filters would not just be accepted, to
+ * fail silently. PERF_SAMPLE_BRANCH_HV is a special case that is selectively
+ * supported only on platforms where kernel is in hyp mode.
+ */
+#define BRBE_EXCLUDE_BRANCH_FILTERS (PERF_SAMPLE_BRANCH_ABORT_TX       | \
+                                    PERF_SAMPLE_BRANCH_IN_TX           | \
+                                    PERF_SAMPLE_BRANCH_NO_TX           | \
+                                    PERF_SAMPLE_BRANCH_CALL_STACK      | \
+                                    PERF_SAMPLE_BRANCH_COUNTERS)
+
+#define BRBE_ALLOWED_BRANCH_TYPES   (PERF_SAMPLE_BRANCH_ANY            | \
+                                    PERF_SAMPLE_BRANCH_ANY_CALL        | \
+                                    PERF_SAMPLE_BRANCH_ANY_RETURN      | \
+                                    PERF_SAMPLE_BRANCH_IND_CALL        | \
+                                    PERF_SAMPLE_BRANCH_COND            | \
+                                    PERF_SAMPLE_BRANCH_IND_JUMP        | \
+                                    PERF_SAMPLE_BRANCH_CALL)
+
+
+#define BRBE_ALLOWED_BRANCH_FILTERS (PERF_SAMPLE_BRANCH_USER           | \
+                                    PERF_SAMPLE_BRANCH_KERNEL          | \
+                                    PERF_SAMPLE_BRANCH_HV              | \
+                                    BRBE_ALLOWED_BRANCH_TYPES          | \
+                                    PERF_SAMPLE_BRANCH_NO_FLAGS        | \
+                                    PERF_SAMPLE_BRANCH_NO_CYCLES       | \
+                                    PERF_SAMPLE_BRANCH_TYPE_SAVE       | \
+                                    PERF_SAMPLE_BRANCH_HW_INDEX        | \
+                                    PERF_SAMPLE_BRANCH_PRIV_SAVE)
+
+#define BRBE_PERF_BRANCH_FILTERS    (BRBE_ALLOWED_BRANCH_FILTERS       | \
+                                    BRBE_EXCLUDE_BRANCH_FILTERS)
+
+/*
+ * BRBE supports the following functional branch type filters while
+ * generating branch records. These branch filters can be enabled,
+ * either individually or as a group i.e ORing multiple filters
+ * with each other.
+ *
+ * BRBFCR_EL1_CONDDIR  - Conditional direct branch
+ * BRBFCR_EL1_DIRCALL  - Direct call
+ * BRBFCR_EL1_INDCALL  - Indirect call
+ * BRBFCR_EL1_INDIRECT - Indirect branch
+ * BRBFCR_EL1_DIRECT   - Direct branch
+ * BRBFCR_EL1_RTN      - Subroutine return
+ */
+static u64 branch_type_to_brbfcr(int branch_type)
+{
+       u64 brbfcr = 0;
+
+       if (branch_type & PERF_SAMPLE_BRANCH_ANY) {
+               brbfcr |= BRBFCR_EL1_BRANCH_FILTERS;
+               return brbfcr;
+       }
+
+       if (branch_type & PERF_SAMPLE_BRANCH_ANY_CALL) {
+               brbfcr |= BRBFCR_EL1_INDCALL;
+               brbfcr |= BRBFCR_EL1_DIRCALL;
+       }
+
+       if (branch_type & PERF_SAMPLE_BRANCH_ANY_RETURN)
+               brbfcr |= BRBFCR_EL1_RTN;
+
+       if (branch_type & PERF_SAMPLE_BRANCH_IND_CALL)
+               brbfcr |= BRBFCR_EL1_INDCALL;
+
+       if (branch_type & PERF_SAMPLE_BRANCH_COND)
+               brbfcr |= BRBFCR_EL1_CONDDIR;
+
+       if (branch_type & PERF_SAMPLE_BRANCH_IND_JUMP)
+               brbfcr |= BRBFCR_EL1_INDIRECT;
+
+       if (branch_type & PERF_SAMPLE_BRANCH_CALL)
+               brbfcr |= BRBFCR_EL1_DIRCALL;
+
+       return brbfcr;
+}
+
+/*
+ * BRBE supports the following privilege mode filters while generating
+ * branch records.
+ *
+ * BRBCR_ELx_E0BRE - EL0 branch records
+ * BRBCR_ELx_ExBRE - EL1/EL2 branch records
+ *
+ * BRBE also supports the following additional functional branch type
+ * filters while generating branch records.
+ *
+ * BRBCR_ELx_EXCEPTION - Exception
+ * BRBCR_ELx_ERTN     -  Exception return
+ */
+static u64 branch_type_to_brbcr(int branch_type)
+{
+       u64 brbcr = BRBCR_ELx_FZP | BRBCR_ELx_DEFAULT_TS;
+
+       if (branch_type & PERF_SAMPLE_BRANCH_USER)
+               brbcr |= BRBCR_ELx_E0BRE;
+
+       /*
+        * When running in the hyp mode, writing into BRBCR_EL1
+        * actually writes into BRBCR_EL2 instead. Field E2BRE
+        * is also at the same position as E1BRE.
+        */
+       if (branch_type & PERF_SAMPLE_BRANCH_KERNEL)
+               brbcr |= BRBCR_ELx_ExBRE;
+
+       if (branch_type & PERF_SAMPLE_BRANCH_HV) {
+               if (is_kernel_in_hyp_mode())
+                       brbcr |= BRBCR_ELx_ExBRE;
+       }
+
+       if (!(branch_type & PERF_SAMPLE_BRANCH_NO_CYCLES))
+               brbcr |= BRBCR_ELx_CC;
+
+       if (!(branch_type & PERF_SAMPLE_BRANCH_NO_FLAGS))
+               brbcr |= BRBCR_ELx_MPRED;
+
+       /*
+        * The exception and exception return branches could be
+        * captured, irrespective of the perf event's privilege.
+        * If the perf event does not have enough privilege for
+        * a given exception level, then addresses which falls
+        * under that exception level will be reported as zero
+        * for the captured branch record, creating source only
+        * or target only records.
+        */
+       if (branch_type & PERF_SAMPLE_BRANCH_KERNEL) {
+               if (branch_type & PERF_SAMPLE_BRANCH_ANY) {
+                       brbcr |= BRBCR_ELx_EXCEPTION;
+                       brbcr |= BRBCR_ELx_ERTN;
+               }
+
+               if (branch_type & PERF_SAMPLE_BRANCH_ANY_CALL)
+                       brbcr |= BRBCR_ELx_EXCEPTION;
+
+               if (branch_type & PERF_SAMPLE_BRANCH_ANY_RETURN)
+                       brbcr |= BRBCR_ELx_ERTN;
+       }
+       return brbcr;
+}
+
+bool brbe_branch_attr_valid(struct perf_event *event)
+{
+       u64 branch_type = event->attr.branch_sample_type;
+
+       /*
+        * Ensure both perf branch filter allowed and exclude
+        * masks are always in sync with the generic perf ABI.
+        */
+       BUILD_BUG_ON(BRBE_PERF_BRANCH_FILTERS != (PERF_SAMPLE_BRANCH_MAX - 1));
+
+       if (branch_type & BRBE_EXCLUDE_BRANCH_FILTERS) {
+               pr_debug("requested branch filter not supported 0x%llx\n", branch_type);
+               return false;
+       }
+
+       /* Ensure at least 1 branch type is enabled */
+       if (!(branch_type & BRBE_ALLOWED_BRANCH_TYPES)) {
+               pr_debug("no branch type enabled 0x%llx\n", branch_type);
+               return false;
+       }
+
+       /*
+        * No branches are recorded in guests nor nVHE hypervisors, so
+        * excluding the host or both kernel and user is invalid.
+        *
+        * Ideally we'd just require exclude_guest and exclude_hv, but setting
+        * event filters with perf for kernel or user don't set exclude_guest.
+        * So effectively, exclude_guest and exclude_hv are ignored.
+        */
+       if (event->attr.exclude_host || (event->attr.exclude_user && event->attr.exclude_kernel)) {
+               pr_debug("branch filter in hypervisor or guest only not supported 0x%llx\n", branch_type);
+               return false;
+       }
+
+       event->hw.branch_reg.config = branch_type_to_brbfcr(event->attr.branch_sample_type);
+       event->hw.extra_reg.config = branch_type_to_brbcr(event->attr.branch_sample_type);
+
+       return true;
+}
+
+unsigned int brbe_num_branch_records(const struct arm_pmu *armpmu)
+{
+       return FIELD_GET(BRBIDR0_EL1_NUMREC_MASK, armpmu->reg_brbidr);
+}
+
+void brbe_probe(struct arm_pmu *armpmu)
+{
+       u64 brbidr, aa64dfr0 = read_sysreg_s(SYS_ID_AA64DFR0_EL1);
+       u32 brbe;
+
+       brbe = cpuid_feature_extract_unsigned_field(aa64dfr0, ID_AA64DFR0_EL1_BRBE_SHIFT);
+       if (!valid_brbe_version(brbe))
+               return;
+
+       brbidr = read_sysreg_s(SYS_BRBIDR0_EL1);
+       if (!valid_brbidr(brbidr))
+               return;
+
+       armpmu->reg_brbidr = brbidr;
+}
+
+/*
+ * BRBE is assumed to be disabled/paused on entry
+ */
+void brbe_enable(const struct arm_pmu *arm_pmu)
+{
+       struct pmu_hw_events *cpuc = this_cpu_ptr(arm_pmu->hw_events);
+       u64 brbfcr = 0, brbcr = 0;
+
+       /*
+        * Discard existing records to avoid a discontinuity, e.g. records
+        * missed during handling an overflow.
+        */
+       brbe_invalidate();
+
+       /*
+        * Merge the permitted branch filters of all events.
+        */
+       for (int i = 0; i < ARMPMU_MAX_HWEVENTS; i++) {
+               struct perf_event *event = cpuc->events[i];
+
+               if (event && has_branch_stack(event)) {
+                       brbfcr |= event->hw.branch_reg.config;
+                       brbcr |= event->hw.extra_reg.config;
+               }
+       }
+
+       /*
+        * In VHE mode with MDCR_EL2.HPMN equal to PMCR_EL0.N, BRBCR_EL1.FZP
+        * controls freezing the branch records on counter overflow rather than
+        * BRBCR_EL2.FZP (which writes to BRBCR_EL1 are redirected to).
+        * The exception levels are enabled/disabled in BRBCR_EL2, so keep EL1
+        * and EL0 recording disabled for guests.
+        *
+        * As BRBCR_EL1 CC and MPRED bits also need to match, use the same
+        * value for both registers just masking the exception levels.
+        */
+       if (is_kernel_in_hyp_mode())
+               write_sysreg_s(brbcr & ~(BRBCR_ELx_ExBRE | BRBCR_ELx_E0BRE), SYS_BRBCR_EL12);
+       write_sysreg_s(brbcr, SYS_BRBCR_EL1);
+       /* Ensure BRBCR_ELx settings take effect before unpausing */
+       isb();
+
+       /* Finally write SYS_BRBFCR_EL to unpause BRBE */
+       write_sysreg_s(brbfcr, SYS_BRBFCR_EL1);
+       /* Synchronization in PMCR write ensures ordering WRT PMU enabling */
+}
+
+void brbe_disable(void)
+{
+       /*
+        * No need for synchronization here as synchronization in PMCR write
+        * ensures ordering and in the interrupt handler this is a NOP as
+        * we're already paused.
+        */
+       write_sysreg_s(BRBFCR_EL1_PAUSED, SYS_BRBFCR_EL1);
+       write_sysreg_s(0, SYS_BRBCR_EL1);
+}
+
+static const int brbe_type_to_perf_type_map[BRBINFx_EL1_TYPE_DEBUG_EXIT + 1][2] = {
+       [BRBINFx_EL1_TYPE_DIRECT_UNCOND] = { PERF_BR_UNCOND, 0 },
+       [BRBINFx_EL1_TYPE_INDIRECT] = { PERF_BR_IND, 0 },
+       [BRBINFx_EL1_TYPE_DIRECT_LINK] = { PERF_BR_CALL, 0 },
+       [BRBINFx_EL1_TYPE_INDIRECT_LINK] = { PERF_BR_IND_CALL, 0 },
+       [BRBINFx_EL1_TYPE_RET] = { PERF_BR_RET, 0 },
+       [BRBINFx_EL1_TYPE_DIRECT_COND] = { PERF_BR_COND, 0 },
+       [BRBINFx_EL1_TYPE_CALL] = { PERF_BR_SYSCALL, 0 },
+       [BRBINFx_EL1_TYPE_ERET] = { PERF_BR_ERET, 0 },
+       [BRBINFx_EL1_TYPE_IRQ] = { PERF_BR_IRQ, 0 },
+       [BRBINFx_EL1_TYPE_TRAP] = { PERF_BR_IRQ, 0 },
+       [BRBINFx_EL1_TYPE_SERROR] = { PERF_BR_SERROR, 0 },
+       [BRBINFx_EL1_TYPE_ALIGN_FAULT] = { PERF_BR_EXTEND_ABI, PERF_BR_NEW_FAULT_ALGN },
+       [BRBINFx_EL1_TYPE_INSN_FAULT] = { PERF_BR_EXTEND_ABI, PERF_BR_NEW_FAULT_INST },
+       [BRBINFx_EL1_TYPE_DATA_FAULT] = { PERF_BR_EXTEND_ABI, PERF_BR_NEW_FAULT_DATA },
+};
+
+static void brbe_set_perf_entry_type(struct perf_branch_entry *entry, u64 brbinf)
+{
+       int brbe_type = brbinf_get_type(brbinf);
+
+       if (brbe_type <= BRBINFx_EL1_TYPE_DEBUG_EXIT) {
+               const int *br_type = brbe_type_to_perf_type_map[brbe_type];
+
+               entry->type = br_type[0];
+               entry->new_type = br_type[1];
+       }
+}
+
+static int brbinf_get_perf_priv(u64 brbinf)
+{
+       int brbe_el = brbinf_get_el(brbinf);
+
+       switch (brbe_el) {
+       case BRBINFx_EL1_EL_EL0:
+               return PERF_BR_PRIV_USER;
+       case BRBINFx_EL1_EL_EL1:
+               return PERF_BR_PRIV_KERNEL;
+       case BRBINFx_EL1_EL_EL2:
+               if (is_kernel_in_hyp_mode())
+                       return PERF_BR_PRIV_KERNEL;
+               return PERF_BR_PRIV_HV;
+       default:
+               pr_warn_once("%d - unknown branch privilege captured\n", brbe_el);
+               return PERF_BR_PRIV_UNKNOWN;
+       }
+}
+
+static bool perf_entry_from_brbe_regset(int index, struct perf_branch_entry *entry,
+                                       const struct perf_event *event)
+{
+       struct brbe_regset bregs;
+       u64 brbinf;
+
+       if (!__read_brbe_regset(&bregs, index))
+               return false;
+
+       brbinf = bregs.brbinf;
+       perf_clear_branch_entry_bitfields(entry);
+       if (brbe_record_is_complete(brbinf)) {
+               entry->from = bregs.brbsrc;
+               entry->to = bregs.brbtgt;
+       } else if (brbe_record_is_source_only(brbinf)) {
+               entry->from = bregs.brbsrc;
+               entry->to = 0;
+       } else if (brbe_record_is_target_only(brbinf)) {
+               entry->from = 0;
+               entry->to = bregs.brbtgt;
+       }
+
+       brbe_set_perf_entry_type(entry, brbinf);
+
+       if (!branch_sample_no_cycles(event))
+               entry->cycles = brbinf_get_cycles(brbinf);
+
+       if (!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);
+                       entry->predicted = !entry->mispred;
+               }
+
+               /*
+                * Currently TME feature is neither implemented in any hardware
+                * nor it is being supported in the kernel. Just warn here once
+                * if TME related information shows up rather unexpectedly.
+                */
+               if (brbinf_get_lastfailed(brbinf) || brbinf_get_in_tx(brbinf))
+                       pr_warn_once("Unknown transaction states\n");
+       }
+
+       /*
+        * Branch privilege level is available for target only and complete
+        * branch records.
+        */
+       if (!brbe_record_is_source_only(brbinf))
+               entry->priv = brbinf_get_perf_priv(brbinf);
+
+       return true;
+}
+
+#define PERF_BR_ARM64_ALL (                            \
+       BIT(PERF_BR_COND) |                             \
+       BIT(PERF_BR_UNCOND) |                           \
+       BIT(PERF_BR_IND) |                              \
+       BIT(PERF_BR_CALL) |                             \
+       BIT(PERF_BR_IND_CALL) |                         \
+       BIT(PERF_BR_RET))
+
+#define PERF_BR_ARM64_ALL_KERNEL (                     \
+       BIT(PERF_BR_SYSCALL) |                          \
+       BIT(PERF_BR_IRQ) |                              \
+       BIT(PERF_BR_SERROR) |                           \
+       BIT(PERF_BR_MAX + PERF_BR_NEW_FAULT_ALGN) |     \
+       BIT(PERF_BR_MAX + PERF_BR_NEW_FAULT_DATA) |     \
+       BIT(PERF_BR_MAX + PERF_BR_NEW_FAULT_INST))
+
+static void prepare_event_branch_type_mask(u64 branch_sample,
+                                          unsigned long *event_type_mask)
+{
+       if (branch_sample & PERF_SAMPLE_BRANCH_ANY) {
+               if (branch_sample & PERF_SAMPLE_BRANCH_KERNEL)
+                       bitmap_from_u64(event_type_mask,
+                               BIT(PERF_BR_ERET) | PERF_BR_ARM64_ALL |
+                               PERF_BR_ARM64_ALL_KERNEL);
+               else
+                       bitmap_from_u64(event_type_mask, PERF_BR_ARM64_ALL);
+               return;
+       }
+
+       bitmap_zero(event_type_mask, PERF_BR_ARM64_MAX);
+
+       if (branch_sample & PERF_SAMPLE_BRANCH_ANY_CALL) {
+               if (branch_sample & PERF_SAMPLE_BRANCH_KERNEL)
+                       bitmap_from_u64(event_type_mask, PERF_BR_ARM64_ALL_KERNEL);
+
+               set_bit(PERF_BR_CALL, event_type_mask);
+               set_bit(PERF_BR_IND_CALL, event_type_mask);
+       }
+
+       if (branch_sample & PERF_SAMPLE_BRANCH_IND_JUMP)
+               set_bit(PERF_BR_IND, event_type_mask);
+
+       if (branch_sample & PERF_SAMPLE_BRANCH_COND)
+               set_bit(PERF_BR_COND, event_type_mask);
+
+       if (branch_sample & PERF_SAMPLE_BRANCH_CALL)
+               set_bit(PERF_BR_CALL, event_type_mask);
+
+       if (branch_sample & PERF_SAMPLE_BRANCH_IND_CALL)
+               set_bit(PERF_BR_IND_CALL, event_type_mask);
+
+       if (branch_sample & PERF_SAMPLE_BRANCH_ANY_RETURN) {
+               set_bit(PERF_BR_RET, event_type_mask);
+
+               if (branch_sample & PERF_SAMPLE_BRANCH_KERNEL)
+                       set_bit(PERF_BR_ERET, event_type_mask);
+       }
+}
+
+/*
+ * BRBE is configured with an OR of permissions from all events, so there may
+ * be events which have to be dropped or events where just the source or target
+ * address has to be zeroed.
+ */
+static bool filter_branch_privilege(struct perf_branch_entry *entry, u64 branch_sample_type)
+{
+       bool from_user = access_ok((void __user *)(unsigned long)entry->from, 4);
+       bool to_user = access_ok((void __user *)(unsigned long)entry->to, 4);
+       bool exclude_kernel = !((branch_sample_type & PERF_SAMPLE_BRANCH_KERNEL) ||
+               (is_kernel_in_hyp_mode() && (branch_sample_type & PERF_SAMPLE_BRANCH_HV)));
+
+       /* We can only have a half record if permissions have not been expanded */
+       if (!entry->from || !entry->to)
+               return true;
+
+       /*
+        * If record is within a single exception level, just need to either
+        * drop or keep the entire record.
+        */
+       if (from_user == to_user)
+               return ((entry->priv == PERF_BR_PRIV_KERNEL) && !exclude_kernel) ||
+                       ((entry->priv == PERF_BR_PRIV_USER) &&
+                        (branch_sample_type & PERF_SAMPLE_BRANCH_USER));
+
+       /*
+        * Record is across exception levels, mask addresses for the exception
+        * level we're not capturing.
+        */
+       if (!(branch_sample_type & PERF_SAMPLE_BRANCH_USER)) {
+               if (from_user)
+                       entry->from = 0;
+               if (to_user)
+                       entry->to = 0;
+       }
+
+       if (exclude_kernel) {
+               if (!from_user)
+                       entry->from = 0;
+               if (!to_user)
+                       entry->to = 0;
+       }
+
+       return true;
+}
+
+static bool filter_branch_type(struct perf_branch_entry *entry,
+                              const unsigned long *event_type_mask)
+{
+       if (entry->type == PERF_BR_EXTEND_ABI)
+               return test_bit(PERF_BR_MAX + entry->new_type, event_type_mask);
+       else
+               return test_bit(entry->type, event_type_mask);
+}
+
+static bool filter_branch_record(struct perf_branch_entry *entry,
+                                u64 branch_sample,
+                                const unsigned long *event_type_mask)
+{
+       return filter_branch_type(entry, event_type_mask) &&
+               filter_branch_privilege(entry, branch_sample);
+}
+
+void brbe_read_filtered_entries(struct perf_branch_stack *branch_stack,
+                               const struct perf_event *event)
+{
+       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);
+
+               select_brbe_bank(bank);
+
+               for (int i = 0; i < nr_this_bank; i++) {
+                       struct perf_branch_entry *pbe = &branch_stack->entries[nr_filtered];
+
+                       if (!perf_entry_from_brbe_regset(i, pbe, event))
+                               goto done;
+
+                       if (!filter_branch_record(pbe, branch_sample_type, event_type_mask))
+                               continue;
+
+                       nr_filtered++;
+               }
+       }
+
+done:
+       branch_stack->nr = nr_filtered;
+}
 
 #include <linux/smp.h>
 #include <linux/nmi.h>
 
+#include "arm_brbe.h"
+
 /* ARMv8 Cortex-A53 specific event types. */
 #define ARMV8_A53_PERFCTR_PREF_LINEFILL                                0xC2
 
 
 static DEVICE_ATTR_RO(threshold_max);
 
+static ssize_t branches_show(struct device *dev,
+                            struct device_attribute *attr, char *page)
+{
+       struct pmu *pmu = dev_get_drvdata(dev);
+       struct arm_pmu *cpu_pmu = container_of(pmu, struct arm_pmu, pmu);
+
+       return sysfs_emit(page, "%d\n", brbe_num_branch_records(cpu_pmu));
+}
+
+static DEVICE_ATTR_RO(branches);
+
 static struct attribute *armv8_pmuv3_caps_attrs[] = {
+       &dev_attr_branches.attr,
        &dev_attr_slots.attr,
        &dev_attr_bus_slots.attr,
        &dev_attr_bus_width.attr,
        NULL,
 };
 
+static umode_t caps_is_visible(struct kobject *kobj, struct attribute *attr, int i)
+{
+       struct device *dev = kobj_to_dev(kobj);
+       struct pmu *pmu = dev_get_drvdata(dev);
+       struct arm_pmu *cpu_pmu = container_of(pmu, struct arm_pmu, pmu);
+
+       if (i == 0)
+               return brbe_num_branch_records(cpu_pmu) ? attr->mode : 0;
+
+       return attr->mode;
+}
+
 static const struct attribute_group armv8_pmuv3_caps_attr_group = {
        .name = "caps",
        .attrs = armv8_pmuv3_caps_attrs,
+       .is_visible = caps_is_visible,
 };
 
 /*
 static void armv8pmu_start(struct arm_pmu *cpu_pmu)
 {
        struct perf_event_context *ctx;
+       struct pmu_hw_events *hw_events = this_cpu_ptr(cpu_pmu->hw_events);
        int nr_user = 0;
 
        ctx = perf_cpu_task_ctx();
 
        kvm_vcpu_pmu_resync_el0();
 
+       if (hw_events->branch_users)
+               brbe_enable(cpu_pmu);
+
        /* Enable all counters */
        armv8pmu_pmcr_write(armv8pmu_pmcr_read() | ARMV8_PMU_PMCR_E);
 }
 
 static void armv8pmu_stop(struct arm_pmu *cpu_pmu)
 {
+       struct pmu_hw_events *hw_events = this_cpu_ptr(cpu_pmu->hw_events);
+
+       if (hw_events->branch_users)
+               brbe_disable();
+
        /* Disable all counters */
        armv8pmu_pmcr_write(armv8pmu_pmcr_read() & ~ARMV8_PMU_PMCR_E);
 }
 
+static void read_branch_records(struct pmu_hw_events *cpuc,
+                               struct perf_event *event,
+                               struct perf_sample_data *data)
+{
+       struct perf_branch_stack *branch_stack = cpuc->branch_stack;
+
+       brbe_read_filtered_entries(branch_stack, event);
+       perf_sample_save_brstack(data, event, branch_stack, NULL);
+}
+
 static irqreturn_t armv8pmu_handle_irq(struct arm_pmu *cpu_pmu)
 {
        u64 pmovsr;
                if (!armpmu_event_set_period(event))
                        continue;
 
+               if (has_branch_stack(event))
+                       read_branch_records(cpuc, event, &data);
+
                /*
                 * Perf event overflow will queue the processing of the event as
                 * an irq_work which will be taken care of in the handling of
 
        /* Always prefer to place a cycle counter into the cycle counter. */
        if ((evtype == ARMV8_PMUV3_PERFCTR_CPU_CYCLES) &&
-           !armv8pmu_event_get_threshold(&event->attr)) {
+           !armv8pmu_event_get_threshold(&event->attr) && !has_branch_stack(event)) {
                if (!test_and_set_bit(ARMV8_PMU_CYCLE_IDX, cpuc->used_mask))
                        return ARMV8_PMU_CYCLE_IDX;
                else if (armv8pmu_event_is_64bit(event) &&
        return event->hw.idx + 1;
 }
 
+static void armv8pmu_sched_task(struct perf_event_pmu_context *pmu_ctx,
+                               struct task_struct *task, bool sched_in)
+{
+       struct arm_pmu *armpmu = *this_cpu_ptr(&cpu_armpmu);
+       struct pmu_hw_events *hw_events = this_cpu_ptr(armpmu->hw_events);
+
+       if (!hw_events->branch_users)
+               return;
+
+       if (sched_in)
+               brbe_invalidate();
+}
+
 /*
  * Add an event filter to a given event.
  */
                return -EOPNOTSUPP;
        }
 
+       if (has_branch_stack(perf_event)) {
+               if (!brbe_num_branch_records(cpu_pmu) || !brbe_branch_attr_valid(perf_event))
+                       return -EOPNOTSUPP;
+
+               perf_event->attach_state |= PERF_ATTACH_SCHED_CB;
+       }
+
        /*
         * If we're running in hyp mode, then we *are* the hypervisor.
         * Therefore we ignore exclude_hv in this configuration, since
        /* Clear the counters we flip at guest entry/exit */
        kvm_clr_pmu_events(mask);
 
+       if (brbe_num_branch_records(cpu_pmu)) {
+               brbe_disable();
+               brbe_invalidate();
+       }
+
        /*
         * Initialize & Reset PMNC. Request overflow interrupt for
         * 64 bit cycle counter but cheat in armv8pmu_write_counter().
                cpu_pmu->reg_pmmir = read_pmmir();
        else
                cpu_pmu->reg_pmmir = 0;
+
+       brbe_probe(cpu_pmu);
+}
+
+static int branch_records_alloc(struct arm_pmu *armpmu)
+{
+       size_t size = struct_size_t(struct perf_branch_stack, entries,
+                                   brbe_num_branch_records(armpmu));
+       int cpu;
+
+       for_each_cpu(cpu, &armpmu->supported_cpus) {
+               struct pmu_hw_events *events_cpu;
+
+               events_cpu = per_cpu_ptr(armpmu->hw_events, cpu);
+               events_cpu->branch_stack = kmalloc(size, GFP_KERNEL);
+               if (!events_cpu->branch_stack)
+                       return -ENOMEM;
+       }
+       return 0;
 }
 
 static int armv8pmu_probe_pmu(struct arm_pmu *cpu_pmu)
        if (ret)
                return ret;
 
-       return probe.present ? 0 : -ENODEV;
+       if (!probe.present)
+               return -ENODEV;
+
+       if (brbe_num_branch_records(cpu_pmu)) {
+               ret = branch_records_alloc(cpu_pmu);
+               if (ret)
+                       return ret;
+       }
+       return 0;
 }
 
 static void armv8pmu_disable_user_access_ipi(void *unused)
        cpu_pmu->set_event_filter       = armv8pmu_set_event_filter;
 
        cpu_pmu->pmu.event_idx          = armv8pmu_user_event_idx;
+       if (brbe_num_branch_records(cpu_pmu))
+               cpu_pmu->pmu.sched_task         = armv8pmu_sched_task;
 
        cpu_pmu->name                   = name;
        cpu_pmu->map_event              = map_event;