| // SPDX-License-Identifier: GPL-2.0 |
| #include <linux/perf_event.h> |
| #include <asm/perf_event.h> |
| |
| #include "../perf_event.h" |
| |
| /* LBR Branch Select valid bits */ |
| #define LBR_SELECT_MASK 0x1ff |
| |
| /* |
| * LBR Branch Select filter bits which when set, ensures that the |
| * corresponding type of branches are not recorded |
| */ |
| #define LBR_SELECT_KERNEL 0 /* Branches ending in CPL = 0 */ |
| #define LBR_SELECT_USER 1 /* Branches ending in CPL > 0 */ |
| #define LBR_SELECT_JCC 2 /* Conditional branches */ |
| #define LBR_SELECT_CALL_NEAR_REL 3 /* Near relative calls */ |
| #define LBR_SELECT_CALL_NEAR_IND 4 /* Indirect relative calls */ |
| #define LBR_SELECT_RET_NEAR 5 /* Near returns */ |
| #define LBR_SELECT_JMP_NEAR_IND 6 /* Near indirect jumps (excl. calls and returns) */ |
| #define LBR_SELECT_JMP_NEAR_REL 7 /* Near relative jumps (excl. calls) */ |
| #define LBR_SELECT_FAR_BRANCH 8 /* Far branches */ |
| |
| #define LBR_KERNEL BIT(LBR_SELECT_KERNEL) |
| #define LBR_USER BIT(LBR_SELECT_USER) |
| #define LBR_JCC BIT(LBR_SELECT_JCC) |
| #define LBR_REL_CALL BIT(LBR_SELECT_CALL_NEAR_REL) |
| #define LBR_IND_CALL BIT(LBR_SELECT_CALL_NEAR_IND) |
| #define LBR_RETURN BIT(LBR_SELECT_RET_NEAR) |
| #define LBR_REL_JMP BIT(LBR_SELECT_JMP_NEAR_REL) |
| #define LBR_IND_JMP BIT(LBR_SELECT_JMP_NEAR_IND) |
| #define LBR_FAR BIT(LBR_SELECT_FAR_BRANCH) |
| #define LBR_NOT_SUPP -1 /* unsupported filter */ |
| #define LBR_IGNORE 0 |
| |
| #define LBR_ANY \ |
| (LBR_JCC | LBR_REL_CALL | LBR_IND_CALL | LBR_RETURN | \ |
| LBR_REL_JMP | LBR_IND_JMP | LBR_FAR) |
| |
| struct branch_entry { |
| union { |
| struct { |
| u64 ip:58; |
| u64 ip_sign_ext:5; |
| u64 mispredict:1; |
| } split; |
| u64 full; |
| } from; |
| |
| union { |
| struct { |
| u64 ip:58; |
| u64 ip_sign_ext:3; |
| u64 reserved:1; |
| u64 spec:1; |
| u64 valid:1; |
| } split; |
| u64 full; |
| } to; |
| }; |
| |
| static __always_inline void amd_pmu_lbr_set_from(unsigned int idx, u64 val) |
| { |
| wrmsrl(MSR_AMD_SAMP_BR_FROM + idx * 2, val); |
| } |
| |
| static __always_inline void amd_pmu_lbr_set_to(unsigned int idx, u64 val) |
| { |
| wrmsrl(MSR_AMD_SAMP_BR_FROM + idx * 2 + 1, val); |
| } |
| |
| static __always_inline u64 amd_pmu_lbr_get_from(unsigned int idx) |
| { |
| u64 val; |
| |
| rdmsrl(MSR_AMD_SAMP_BR_FROM + idx * 2, val); |
| |
| return val; |
| } |
| |
| static __always_inline u64 amd_pmu_lbr_get_to(unsigned int idx) |
| { |
| u64 val; |
| |
| rdmsrl(MSR_AMD_SAMP_BR_FROM + idx * 2 + 1, val); |
| |
| return val; |
| } |
| |
| static __always_inline u64 sign_ext_branch_ip(u64 ip) |
| { |
| u32 shift = 64 - boot_cpu_data.x86_virt_bits; |
| |
| return (u64)(((s64)ip << shift) >> shift); |
| } |
| |
| static void amd_pmu_lbr_filter(void) |
| { |
| struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); |
| int br_sel = cpuc->br_sel, offset, type, i, j; |
| bool compress = false; |
| bool fused_only = false; |
| u64 from, to; |
| |
| /* If sampling all branches, there is nothing to filter */ |
| if (((br_sel & X86_BR_ALL) == X86_BR_ALL) && |
| ((br_sel & X86_BR_TYPE_SAVE) != X86_BR_TYPE_SAVE)) |
| fused_only = true; |
| |
| for (i = 0; i < cpuc->lbr_stack.nr; i++) { |
| from = cpuc->lbr_entries[i].from; |
| to = cpuc->lbr_entries[i].to; |
| type = branch_type_fused(from, to, 0, &offset); |
| |
| /* |
| * Adjust the branch from address in case of instruction |
| * fusion where it points to an instruction preceding the |
| * actual branch |
| */ |
| if (offset) { |
| cpuc->lbr_entries[i].from += offset; |
| if (fused_only) |
| continue; |
| } |
| |
| /* If type does not correspond, then discard */ |
| if (type == X86_BR_NONE || (br_sel & type) != type) { |
| cpuc->lbr_entries[i].from = 0; /* mark invalid */ |
| compress = true; |
| } |
| |
| if ((br_sel & X86_BR_TYPE_SAVE) == X86_BR_TYPE_SAVE) |
| cpuc->lbr_entries[i].type = common_branch_type(type); |
| } |
| |
| if (!compress) |
| return; |
| |
| /* Remove all invalid entries */ |
| for (i = 0; i < cpuc->lbr_stack.nr; ) { |
| if (!cpuc->lbr_entries[i].from) { |
| j = i; |
| while (++j < cpuc->lbr_stack.nr) |
| cpuc->lbr_entries[j - 1] = cpuc->lbr_entries[j]; |
| cpuc->lbr_stack.nr--; |
| if (!cpuc->lbr_entries[i].from) |
| continue; |
| } |
| i++; |
| } |
| } |
| |
| static const int lbr_spec_map[PERF_BR_SPEC_MAX] = { |
| PERF_BR_SPEC_NA, |
| PERF_BR_SPEC_WRONG_PATH, |
| PERF_BR_NON_SPEC_CORRECT_PATH, |
| PERF_BR_SPEC_CORRECT_PATH, |
| }; |
| |
| void amd_pmu_lbr_read(void) |
| { |
| struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); |
| struct perf_branch_entry *br = cpuc->lbr_entries; |
| struct branch_entry entry; |
| int out = 0, idx, i; |
| |
| if (!cpuc->lbr_users) |
| return; |
| |
| for (i = 0; i < x86_pmu.lbr_nr; i++) { |
| entry.from.full = amd_pmu_lbr_get_from(i); |
| entry.to.full = amd_pmu_lbr_get_to(i); |
| |
| /* |
| * Check if a branch has been logged; if valid = 0, spec = 0 |
| * then no branch was recorded |
| */ |
| if (!entry.to.split.valid && !entry.to.split.spec) |
| continue; |
| |
| perf_clear_branch_entry_bitfields(br + out); |
| |
| br[out].from = sign_ext_branch_ip(entry.from.split.ip); |
| br[out].to = sign_ext_branch_ip(entry.to.split.ip); |
| br[out].mispred = entry.from.split.mispredict; |
| br[out].predicted = !br[out].mispred; |
| |
| /* |
| * Set branch speculation information using the status of |
| * the valid and spec bits. |
| * |
| * When valid = 0, spec = 0, no branch was recorded and the |
| * entry is discarded as seen above. |
| * |
| * When valid = 0, spec = 1, the recorded branch was |
| * speculative but took the wrong path. |
| * |
| * When valid = 1, spec = 0, the recorded branch was |
| * non-speculative but took the correct path. |
| * |
| * When valid = 1, spec = 1, the recorded branch was |
| * speculative and took the correct path |
| */ |
| idx = (entry.to.split.valid << 1) | entry.to.split.spec; |
| br[out].spec = lbr_spec_map[idx]; |
| out++; |
| } |
| |
| cpuc->lbr_stack.nr = out; |
| |
| /* |
| * Internal register renaming always ensures that LBR From[0] and |
| * LBR To[0] always represent the TOS |
| */ |
| cpuc->lbr_stack.hw_idx = 0; |
| |
| /* Perform further software filtering */ |
| amd_pmu_lbr_filter(); |
| } |
| |
| static const int lbr_select_map[PERF_SAMPLE_BRANCH_MAX_SHIFT] = { |
| [PERF_SAMPLE_BRANCH_USER_SHIFT] = LBR_USER, |
| [PERF_SAMPLE_BRANCH_KERNEL_SHIFT] = LBR_KERNEL, |
| [PERF_SAMPLE_BRANCH_HV_SHIFT] = LBR_IGNORE, |
| |
| [PERF_SAMPLE_BRANCH_ANY_SHIFT] = LBR_ANY, |
| [PERF_SAMPLE_BRANCH_ANY_CALL_SHIFT] = LBR_REL_CALL | LBR_IND_CALL | LBR_FAR, |
| [PERF_SAMPLE_BRANCH_ANY_RETURN_SHIFT] = LBR_RETURN | LBR_FAR, |
| [PERF_SAMPLE_BRANCH_IND_CALL_SHIFT] = LBR_IND_CALL, |
| [PERF_SAMPLE_BRANCH_ABORT_TX_SHIFT] = LBR_NOT_SUPP, |
| [PERF_SAMPLE_BRANCH_IN_TX_SHIFT] = LBR_NOT_SUPP, |
| [PERF_SAMPLE_BRANCH_NO_TX_SHIFT] = LBR_NOT_SUPP, |
| [PERF_SAMPLE_BRANCH_COND_SHIFT] = LBR_JCC, |
| |
| [PERF_SAMPLE_BRANCH_CALL_STACK_SHIFT] = LBR_NOT_SUPP, |
| [PERF_SAMPLE_BRANCH_IND_JUMP_SHIFT] = LBR_IND_JMP, |
| [PERF_SAMPLE_BRANCH_CALL_SHIFT] = LBR_REL_CALL, |
| |
| [PERF_SAMPLE_BRANCH_NO_FLAGS_SHIFT] = LBR_NOT_SUPP, |
| [PERF_SAMPLE_BRANCH_NO_CYCLES_SHIFT] = LBR_NOT_SUPP, |
| }; |
| |
| static int amd_pmu_lbr_setup_filter(struct perf_event *event) |
| { |
| struct hw_perf_event_extra *reg = &event->hw.branch_reg; |
| u64 br_type = event->attr.branch_sample_type; |
| u64 mask = 0, v; |
| int i; |
| |
| /* No LBR support */ |
| if (!x86_pmu.lbr_nr) |
| return -EOPNOTSUPP; |
| |
| if (br_type & PERF_SAMPLE_BRANCH_USER) |
| mask |= X86_BR_USER; |
| |
| if (br_type & PERF_SAMPLE_BRANCH_KERNEL) |
| mask |= X86_BR_KERNEL; |
| |
| /* Ignore BRANCH_HV here */ |
| |
| if (br_type & PERF_SAMPLE_BRANCH_ANY) |
| mask |= X86_BR_ANY; |
| |
| if (br_type & PERF_SAMPLE_BRANCH_ANY_CALL) |
| mask |= X86_BR_ANY_CALL; |
| |
| if (br_type & PERF_SAMPLE_BRANCH_ANY_RETURN) |
| mask |= X86_BR_RET | X86_BR_IRET | X86_BR_SYSRET; |
| |
| if (br_type & PERF_SAMPLE_BRANCH_IND_CALL) |
| mask |= X86_BR_IND_CALL; |
| |
| if (br_type & PERF_SAMPLE_BRANCH_COND) |
| mask |= X86_BR_JCC; |
| |
| if (br_type & PERF_SAMPLE_BRANCH_IND_JUMP) |
| mask |= X86_BR_IND_JMP; |
| |
| if (br_type & PERF_SAMPLE_BRANCH_CALL) |
| mask |= X86_BR_CALL | X86_BR_ZERO_CALL; |
| |
| if (br_type & PERF_SAMPLE_BRANCH_TYPE_SAVE) |
| mask |= X86_BR_TYPE_SAVE; |
| |
| reg->reg = mask; |
| mask = 0; |
| |
| for (i = 0; i < PERF_SAMPLE_BRANCH_MAX_SHIFT; i++) { |
| if (!(br_type & BIT_ULL(i))) |
| continue; |
| |
| v = lbr_select_map[i]; |
| if (v == LBR_NOT_SUPP) |
| return -EOPNOTSUPP; |
| |
| if (v != LBR_IGNORE) |
| mask |= v; |
| } |
| |
| /* Filter bits operate in suppress mode */ |
| reg->config = mask ^ LBR_SELECT_MASK; |
| |
| return 0; |
| } |
| |
| int amd_pmu_lbr_hw_config(struct perf_event *event) |
| { |
| int ret = 0; |
| |
| /* LBR is not recommended in counting mode */ |
| if (!is_sampling_event(event)) |
| return -EINVAL; |
| |
| ret = amd_pmu_lbr_setup_filter(event); |
| if (!ret) |
| event->attach_state |= PERF_ATTACH_SCHED_CB; |
| |
| return ret; |
| } |
| |
| void amd_pmu_lbr_reset(void) |
| { |
| struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); |
| int i; |
| |
| if (!x86_pmu.lbr_nr) |
| return; |
| |
| /* Reset all branch records individually */ |
| for (i = 0; i < x86_pmu.lbr_nr; i++) { |
| amd_pmu_lbr_set_from(i, 0); |
| amd_pmu_lbr_set_to(i, 0); |
| } |
| |
| cpuc->last_task_ctx = NULL; |
| cpuc->last_log_id = 0; |
| wrmsrl(MSR_AMD64_LBR_SELECT, 0); |
| } |
| |
| void amd_pmu_lbr_add(struct perf_event *event) |
| { |
| struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); |
| struct hw_perf_event_extra *reg = &event->hw.branch_reg; |
| |
| if (!x86_pmu.lbr_nr) |
| return; |
| |
| if (has_branch_stack(event)) { |
| cpuc->lbr_select = 1; |
| cpuc->lbr_sel->config = reg->config; |
| cpuc->br_sel = reg->reg; |
| } |
| |
| perf_sched_cb_inc(event->ctx->pmu); |
| |
| if (!cpuc->lbr_users++ && !event->total_time_running) |
| amd_pmu_lbr_reset(); |
| } |
| |
| void amd_pmu_lbr_del(struct perf_event *event) |
| { |
| struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); |
| |
| if (!x86_pmu.lbr_nr) |
| return; |
| |
| if (has_branch_stack(event)) |
| cpuc->lbr_select = 0; |
| |
| cpuc->lbr_users--; |
| WARN_ON_ONCE(cpuc->lbr_users < 0); |
| perf_sched_cb_dec(event->ctx->pmu); |
| } |
| |
| void amd_pmu_lbr_sched_task(struct perf_event_context *ctx, bool sched_in) |
| { |
| struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); |
| |
| /* |
| * A context switch can flip the address space and LBR entries are |
| * not tagged with an identifier. Hence, branches cannot be resolved |
| * from the old address space and the LBR records should be wiped. |
| */ |
| if (cpuc->lbr_users && sched_in) |
| amd_pmu_lbr_reset(); |
| } |
| |
| void amd_pmu_lbr_enable_all(void) |
| { |
| struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); |
| u64 lbr_select, dbg_ctl, dbg_extn_cfg; |
| |
| if (!cpuc->lbr_users || !x86_pmu.lbr_nr) |
| return; |
| |
| /* Set hardware branch filter */ |
| if (cpuc->lbr_select) { |
| lbr_select = cpuc->lbr_sel->config & LBR_SELECT_MASK; |
| wrmsrl(MSR_AMD64_LBR_SELECT, lbr_select); |
| } |
| |
| rdmsrl(MSR_IA32_DEBUGCTLMSR, dbg_ctl); |
| rdmsrl(MSR_AMD_DBG_EXTN_CFG, dbg_extn_cfg); |
| |
| wrmsrl(MSR_IA32_DEBUGCTLMSR, dbg_ctl | DEBUGCTLMSR_FREEZE_LBRS_ON_PMI); |
| wrmsrl(MSR_AMD_DBG_EXTN_CFG, dbg_extn_cfg | DBG_EXTN_CFG_LBRV2EN); |
| } |
| |
| void amd_pmu_lbr_disable_all(void) |
| { |
| struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); |
| u64 dbg_ctl, dbg_extn_cfg; |
| |
| if (!cpuc->lbr_users || !x86_pmu.lbr_nr) |
| return; |
| |
| rdmsrl(MSR_AMD_DBG_EXTN_CFG, dbg_extn_cfg); |
| rdmsrl(MSR_IA32_DEBUGCTLMSR, dbg_ctl); |
| |
| wrmsrl(MSR_AMD_DBG_EXTN_CFG, dbg_extn_cfg & ~DBG_EXTN_CFG_LBRV2EN); |
| wrmsrl(MSR_IA32_DEBUGCTLMSR, dbg_ctl & ~DEBUGCTLMSR_FREEZE_LBRS_ON_PMI); |
| } |
| |
| __init int amd_pmu_lbr_init(void) |
| { |
| union cpuid_0x80000022_ebx ebx; |
| |
| if (x86_pmu.version < 2 || !boot_cpu_has(X86_FEATURE_AMD_LBR_V2)) |
| return -EOPNOTSUPP; |
| |
| /* Set number of entries */ |
| ebx.full = cpuid_ebx(EXT_PERFMON_DEBUG_FEATURES); |
| x86_pmu.lbr_nr = ebx.split.lbr_v2_stack_sz; |
| |
| pr_cont("%d-deep LBR, ", x86_pmu.lbr_nr); |
| |
| return 0; |
| } |