#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)
#define BRBCR_ELx_DEFAULT_TS FIELD_PREP(BRBCR_ELx_TS_MASK, BRBCR_ELx_TS_VIRTUAL)
#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;
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)
{
isb();
asm volatile(BRB_IALL_INSN);
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);
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;
}
#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)
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;
}
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;
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;
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;
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;
}
if (!(branch_type & BRBE_ALLOWED_BRANCH_TYPES)) {
pr_debug("no branch type enabled 0x%llx\n", branch_type);
return false;
}
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;
}
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;
brbe_invalidate();
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;
}
}
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);
isb();
write_sysreg_s(brbfcr, SYS_BRBFCR_EL1);
}
void brbe_disable(void)
{
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)) {
if (!brbe_record_is_target_only(brbinf)) {
entry->mispred = brbinf_get_mispredict(brbinf);
entry->predicted = !entry->mispred;
}
if (brbinf_get_lastfailed(brbinf) || brbinf_get_in_tx(brbinf))
pr_warn_once("Unknown transaction states\n");
}
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);
}
}
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)));
if (!entry->from || !entry->to)
return true;
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));
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;
}