Merge 17ca7fc22f ("Merge tag 'perf-core-2024-05-13' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip") into android-mainline
Steps on the way to 6.10-rc1 Change-Id: I486e007717a658d92359d266ae6954000139fda4 Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
This commit is contained in:
@@ -626,7 +626,7 @@ int hw_breakpoint_arch_parse(struct perf_event *bp,
|
||||
hw->address &= ~alignment_mask;
|
||||
hw->ctrl.len <<= offset;
|
||||
|
||||
if (uses_default_overflow_handler(bp)) {
|
||||
if (is_default_overflow_handler(bp)) {
|
||||
/*
|
||||
* Mismatch breakpoints are required for single-stepping
|
||||
* breakpoints.
|
||||
@@ -798,7 +798,7 @@ static void watchpoint_handler(unsigned long addr, unsigned int fsr,
|
||||
* Otherwise, insert a temporary mismatch breakpoint so that
|
||||
* we can single-step over the watchpoint trigger.
|
||||
*/
|
||||
if (!uses_default_overflow_handler(wp))
|
||||
if (!is_default_overflow_handler(wp))
|
||||
continue;
|
||||
step:
|
||||
enable_single_step(wp, instruction_pointer(regs));
|
||||
@@ -811,7 +811,7 @@ step:
|
||||
info->trigger = addr;
|
||||
pr_debug("watchpoint fired: address = 0x%x\n", info->trigger);
|
||||
perf_bp_event(wp, regs);
|
||||
if (uses_default_overflow_handler(wp))
|
||||
if (is_default_overflow_handler(wp))
|
||||
enable_single_step(wp, instruction_pointer(regs));
|
||||
}
|
||||
|
||||
@@ -886,7 +886,7 @@ static void breakpoint_handler(unsigned long unknown, struct pt_regs *regs)
|
||||
info->trigger = addr;
|
||||
pr_debug("breakpoint fired: address = 0x%x\n", addr);
|
||||
perf_bp_event(bp, regs);
|
||||
if (uses_default_overflow_handler(bp))
|
||||
if (is_default_overflow_handler(bp))
|
||||
enable_single_step(bp, addr);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
@@ -655,7 +655,7 @@ static int breakpoint_handler(unsigned long unused, unsigned long esr,
|
||||
perf_bp_event(bp, regs);
|
||||
|
||||
/* Do we need to handle the stepping? */
|
||||
if (uses_default_overflow_handler(bp))
|
||||
if (is_default_overflow_handler(bp))
|
||||
step = 1;
|
||||
unlock:
|
||||
rcu_read_unlock();
|
||||
@@ -734,7 +734,7 @@ static u64 get_distance_from_watchpoint(unsigned long addr, u64 val,
|
||||
static int watchpoint_report(struct perf_event *wp, unsigned long addr,
|
||||
struct pt_regs *regs)
|
||||
{
|
||||
int step = uses_default_overflow_handler(wp);
|
||||
int step = is_default_overflow_handler(wp);
|
||||
struct arch_hw_breakpoint *info = counter_arch_bp(wp);
|
||||
|
||||
info->trigger = addr;
|
||||
|
||||
@@ -647,7 +647,7 @@ static void amd_pmu_cpu_dead(int cpu)
|
||||
}
|
||||
}
|
||||
|
||||
static inline void amd_pmu_set_global_ctl(u64 ctl)
|
||||
static __always_inline void amd_pmu_set_global_ctl(u64 ctl)
|
||||
{
|
||||
wrmsrl(MSR_AMD64_PERF_CNTR_GLOBAL_CTL, ctl);
|
||||
}
|
||||
@@ -907,6 +907,37 @@ static int amd_pmu_handle_irq(struct pt_regs *regs)
|
||||
return amd_pmu_adjust_nmi_window(handled);
|
||||
}
|
||||
|
||||
/*
|
||||
* AMD-specific callback invoked through perf_snapshot_branch_stack static
|
||||
* call, defined in include/linux/perf_event.h. See its definition for API
|
||||
* details. It's up to caller to provide enough space in *entries* to fit all
|
||||
* LBR records, otherwise returned result will be truncated to *cnt* entries.
|
||||
*/
|
||||
static int amd_pmu_v2_snapshot_branch_stack(struct perf_branch_entry *entries, unsigned int cnt)
|
||||
{
|
||||
struct cpu_hw_events *cpuc;
|
||||
unsigned long flags;
|
||||
|
||||
/*
|
||||
* The sequence of steps to freeze LBR should be completely inlined
|
||||
* and contain no branches to minimize contamination of LBR snapshot
|
||||
*/
|
||||
local_irq_save(flags);
|
||||
amd_pmu_core_disable_all();
|
||||
__amd_pmu_lbr_disable();
|
||||
|
||||
cpuc = this_cpu_ptr(&cpu_hw_events);
|
||||
|
||||
amd_pmu_lbr_read();
|
||||
cnt = min(cnt, x86_pmu.lbr_nr);
|
||||
memcpy(entries, cpuc->lbr_entries, sizeof(struct perf_branch_entry) * cnt);
|
||||
|
||||
amd_pmu_v2_enable_all(0);
|
||||
local_irq_restore(flags);
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
static int amd_pmu_v2_handle_irq(struct pt_regs *regs)
|
||||
{
|
||||
struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events);
|
||||
@@ -1443,6 +1474,10 @@ static int __init amd_core_pmu_init(void)
|
||||
static_call_update(amd_pmu_branch_reset, amd_pmu_lbr_reset);
|
||||
static_call_update(amd_pmu_branch_add, amd_pmu_lbr_add);
|
||||
static_call_update(amd_pmu_branch_del, amd_pmu_lbr_del);
|
||||
|
||||
/* Only support branch_stack snapshot on perfmon v2 */
|
||||
if (x86_pmu.handle_irq == amd_pmu_v2_handle_irq)
|
||||
static_call_update(perf_snapshot_branch_stack, amd_pmu_v2_snapshot_branch_stack);
|
||||
} else if (!amd_brs_init()) {
|
||||
/*
|
||||
* BRS requires special event constraints and flushing on ctxsw.
|
||||
|
||||
@@ -310,10 +310,6 @@ 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;
|
||||
@@ -414,18 +410,11 @@ void amd_pmu_lbr_enable_all(void)
|
||||
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);
|
||||
wrmsrl(MSR_AMD_DBG_EXTN_CFG, dbg_extn_cfg & ~DBG_EXTN_CFG_LBRV2EN);
|
||||
|
||||
if (cpu_feature_enabled(X86_FEATURE_AMD_LBR_PMC_FREEZE)) {
|
||||
rdmsrl(MSR_IA32_DEBUGCTLMSR, dbg_ctl);
|
||||
wrmsrl(MSR_IA32_DEBUGCTLMSR, dbg_ctl & ~DEBUGCTLMSR_FREEZE_LBRS_ON_PMI);
|
||||
}
|
||||
__amd_pmu_lbr_disable();
|
||||
}
|
||||
|
||||
__init int amd_pmu_lbr_init(void)
|
||||
|
||||
@@ -1329,6 +1329,19 @@ void amd_pmu_lbr_enable_all(void);
|
||||
void amd_pmu_lbr_disable_all(void);
|
||||
int amd_pmu_lbr_hw_config(struct perf_event *event);
|
||||
|
||||
static __always_inline void __amd_pmu_lbr_disable(void)
|
||||
{
|
||||
u64 dbg_ctl, dbg_extn_cfg;
|
||||
|
||||
rdmsrl(MSR_AMD_DBG_EXTN_CFG, dbg_extn_cfg);
|
||||
wrmsrl(MSR_AMD_DBG_EXTN_CFG, dbg_extn_cfg & ~DBG_EXTN_CFG_LBRV2EN);
|
||||
|
||||
if (cpu_feature_enabled(X86_FEATURE_AMD_LBR_PMC_FREEZE)) {
|
||||
rdmsrl(MSR_IA32_DEBUGCTLMSR, dbg_ctl);
|
||||
wrmsrl(MSR_IA32_DEBUGCTLMSR, dbg_ctl & ~DEBUGCTLMSR_FREEZE_LBRS_ON_PMI);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PERF_EVENTS_AMD_BRS
|
||||
|
||||
#define AMD_FAM19H_BRS_EVENT 0xc4 /* RETIRED_TAKEN_BRANCH_INSTRUCTIONS */
|
||||
|
||||
@@ -675,10 +675,8 @@ static const struct attribute_group *rapl_attr_update[] = {
|
||||
static int __init init_rapl_pmus(void)
|
||||
{
|
||||
int maxdie = topology_max_packages() * topology_max_dies_per_package();
|
||||
size_t size;
|
||||
|
||||
size = sizeof(*rapl_pmus) + maxdie * sizeof(struct rapl_pmu *);
|
||||
rapl_pmus = kzalloc(size, GFP_KERNEL);
|
||||
rapl_pmus = kzalloc(struct_size(rapl_pmus, pmus, maxdie), GFP_KERNEL);
|
||||
if (!rapl_pmus)
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -808,6 +806,9 @@ static const struct x86_cpu_id rapl_model_match[] __initconst = {
|
||||
X86_MATCH_INTEL_FAM6_MODEL(RAPTORLAKE_S, &model_skl),
|
||||
X86_MATCH_INTEL_FAM6_MODEL(METEORLAKE, &model_skl),
|
||||
X86_MATCH_INTEL_FAM6_MODEL(METEORLAKE_L, &model_skl),
|
||||
X86_MATCH_INTEL_FAM6_MODEL(ARROWLAKE_H, &model_skl),
|
||||
X86_MATCH_INTEL_FAM6_MODEL(ARROWLAKE, &model_skl),
|
||||
X86_MATCH_INTEL_FAM6_MODEL(LUNARLAKE_M, &model_skl),
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(x86cpu, rapl_model_match);
|
||||
|
||||
@@ -86,11 +86,7 @@ static __always_inline int arch_atomic_add_return(int i, atomic_t *v)
|
||||
}
|
||||
#define arch_atomic_add_return arch_atomic_add_return
|
||||
|
||||
static __always_inline int arch_atomic_sub_return(int i, atomic_t *v)
|
||||
{
|
||||
return arch_atomic_add_return(-i, v);
|
||||
}
|
||||
#define arch_atomic_sub_return arch_atomic_sub_return
|
||||
#define arch_atomic_sub_return(i, v) arch_atomic_add_return(-(i), v)
|
||||
|
||||
static __always_inline int arch_atomic_fetch_add(int i, atomic_t *v)
|
||||
{
|
||||
@@ -98,11 +94,7 @@ static __always_inline int arch_atomic_fetch_add(int i, atomic_t *v)
|
||||
}
|
||||
#define arch_atomic_fetch_add arch_atomic_fetch_add
|
||||
|
||||
static __always_inline int arch_atomic_fetch_sub(int i, atomic_t *v)
|
||||
{
|
||||
return xadd(&v->counter, -i);
|
||||
}
|
||||
#define arch_atomic_fetch_sub arch_atomic_fetch_sub
|
||||
#define arch_atomic_fetch_sub(i, v) arch_atomic_fetch_add(-(i), v)
|
||||
|
||||
static __always_inline int arch_atomic_cmpxchg(atomic_t *v, int old, int new)
|
||||
{
|
||||
|
||||
@@ -14,6 +14,32 @@ typedef struct {
|
||||
|
||||
#define ATOMIC64_INIT(val) { (val) }
|
||||
|
||||
/*
|
||||
* Read an atomic64_t non-atomically.
|
||||
*
|
||||
* This is intended to be used in cases where a subsequent atomic operation
|
||||
* will handle the torn value, and can be used to prime the first iteration
|
||||
* of unconditional try_cmpxchg() loops, e.g.:
|
||||
*
|
||||
* s64 val = arch_atomic64_read_nonatomic(v);
|
||||
* do { } while (!arch_atomic64_try_cmpxchg(v, &val, val OP i);
|
||||
*
|
||||
* This is NOT safe to use where the value is not always checked by a
|
||||
* subsequent atomic operation, such as in conditional try_cmpxchg() loops
|
||||
* that can break before the atomic operation, e.g.:
|
||||
*
|
||||
* s64 val = arch_atomic64_read_nonatomic(v);
|
||||
* do {
|
||||
* if (condition(val))
|
||||
* break;
|
||||
* } while (!arch_atomic64_try_cmpxchg(v, &val, val OP i);
|
||||
*/
|
||||
static __always_inline s64 arch_atomic64_read_nonatomic(const atomic64_t *v)
|
||||
{
|
||||
/* See comment in arch_atomic_read(). */
|
||||
return __READ_ONCE(v->counter);
|
||||
}
|
||||
|
||||
#define __ATOMIC64_DECL(sym) void atomic64_##sym(atomic64_t *, ...)
|
||||
#ifndef ATOMIC64_EXPORT
|
||||
#define ATOMIC64_DECL_ONE __ATOMIC64_DECL
|
||||
@@ -61,12 +87,18 @@ ATOMIC64_DECL(add_unless);
|
||||
#undef __ATOMIC64_DECL
|
||||
#undef ATOMIC64_EXPORT
|
||||
|
||||
static __always_inline s64 arch_atomic64_cmpxchg(atomic64_t *v, s64 o, s64 n)
|
||||
static __always_inline s64 arch_atomic64_cmpxchg(atomic64_t *v, s64 old, s64 new)
|
||||
{
|
||||
return arch_cmpxchg64(&v->counter, o, n);
|
||||
return arch_cmpxchg64(&v->counter, old, new);
|
||||
}
|
||||
#define arch_atomic64_cmpxchg arch_atomic64_cmpxchg
|
||||
|
||||
static __always_inline bool arch_atomic64_try_cmpxchg(atomic64_t *v, s64 *old, s64 new)
|
||||
{
|
||||
return arch_try_cmpxchg64(&v->counter, old, new);
|
||||
}
|
||||
#define arch_atomic64_try_cmpxchg arch_atomic64_try_cmpxchg
|
||||
|
||||
static __always_inline s64 arch_atomic64_xchg(atomic64_t *v, s64 n)
|
||||
{
|
||||
s64 o;
|
||||
@@ -195,69 +227,62 @@ static __always_inline s64 arch_atomic64_dec_if_positive(atomic64_t *v)
|
||||
|
||||
static __always_inline void arch_atomic64_and(s64 i, atomic64_t *v)
|
||||
{
|
||||
s64 old, c = 0;
|
||||
s64 val = arch_atomic64_read_nonatomic(v);
|
||||
|
||||
while ((old = arch_atomic64_cmpxchg(v, c, c & i)) != c)
|
||||
c = old;
|
||||
do { } while (!arch_atomic64_try_cmpxchg(v, &val, val & i));
|
||||
}
|
||||
|
||||
static __always_inline s64 arch_atomic64_fetch_and(s64 i, atomic64_t *v)
|
||||
{
|
||||
s64 old, c = 0;
|
||||
s64 val = arch_atomic64_read_nonatomic(v);
|
||||
|
||||
while ((old = arch_atomic64_cmpxchg(v, c, c & i)) != c)
|
||||
c = old;
|
||||
do { } while (!arch_atomic64_try_cmpxchg(v, &val, val & i));
|
||||
|
||||
return old;
|
||||
return val;
|
||||
}
|
||||
#define arch_atomic64_fetch_and arch_atomic64_fetch_and
|
||||
|
||||
static __always_inline void arch_atomic64_or(s64 i, atomic64_t *v)
|
||||
{
|
||||
s64 old, c = 0;
|
||||
s64 val = arch_atomic64_read_nonatomic(v);
|
||||
|
||||
while ((old = arch_atomic64_cmpxchg(v, c, c | i)) != c)
|
||||
c = old;
|
||||
do { } while (!arch_atomic64_try_cmpxchg(v, &val, val | i));
|
||||
}
|
||||
|
||||
static __always_inline s64 arch_atomic64_fetch_or(s64 i, atomic64_t *v)
|
||||
{
|
||||
s64 old, c = 0;
|
||||
s64 val = arch_atomic64_read_nonatomic(v);
|
||||
|
||||
while ((old = arch_atomic64_cmpxchg(v, c, c | i)) != c)
|
||||
c = old;
|
||||
do { } while (!arch_atomic64_try_cmpxchg(v, &val, val | i));
|
||||
|
||||
return old;
|
||||
return val;
|
||||
}
|
||||
#define arch_atomic64_fetch_or arch_atomic64_fetch_or
|
||||
|
||||
static __always_inline void arch_atomic64_xor(s64 i, atomic64_t *v)
|
||||
{
|
||||
s64 old, c = 0;
|
||||
s64 val = arch_atomic64_read_nonatomic(v);
|
||||
|
||||
while ((old = arch_atomic64_cmpxchg(v, c, c ^ i)) != c)
|
||||
c = old;
|
||||
do { } while (!arch_atomic64_try_cmpxchg(v, &val, val ^ i));
|
||||
}
|
||||
|
||||
static __always_inline s64 arch_atomic64_fetch_xor(s64 i, atomic64_t *v)
|
||||
{
|
||||
s64 old, c = 0;
|
||||
s64 val = arch_atomic64_read_nonatomic(v);
|
||||
|
||||
while ((old = arch_atomic64_cmpxchg(v, c, c ^ i)) != c)
|
||||
c = old;
|
||||
do { } while (!arch_atomic64_try_cmpxchg(v, &val, val ^ i));
|
||||
|
||||
return old;
|
||||
return val;
|
||||
}
|
||||
#define arch_atomic64_fetch_xor arch_atomic64_fetch_xor
|
||||
|
||||
static __always_inline s64 arch_atomic64_fetch_add(s64 i, atomic64_t *v)
|
||||
{
|
||||
s64 old, c = 0;
|
||||
s64 val = arch_atomic64_read_nonatomic(v);
|
||||
|
||||
while ((old = arch_atomic64_cmpxchg(v, c, c + i)) != c)
|
||||
c = old;
|
||||
do { } while (!arch_atomic64_try_cmpxchg(v, &val, val + i));
|
||||
|
||||
return old;
|
||||
return val;
|
||||
}
|
||||
#define arch_atomic64_fetch_add arch_atomic64_fetch_add
|
||||
|
||||
|
||||
@@ -80,11 +80,7 @@ static __always_inline s64 arch_atomic64_add_return(s64 i, atomic64_t *v)
|
||||
}
|
||||
#define arch_atomic64_add_return arch_atomic64_add_return
|
||||
|
||||
static __always_inline s64 arch_atomic64_sub_return(s64 i, atomic64_t *v)
|
||||
{
|
||||
return arch_atomic64_add_return(-i, v);
|
||||
}
|
||||
#define arch_atomic64_sub_return arch_atomic64_sub_return
|
||||
#define arch_atomic64_sub_return(i, v) arch_atomic64_add_return(-(i), v)
|
||||
|
||||
static __always_inline s64 arch_atomic64_fetch_add(s64 i, atomic64_t *v)
|
||||
{
|
||||
@@ -92,11 +88,7 @@ static __always_inline s64 arch_atomic64_fetch_add(s64 i, atomic64_t *v)
|
||||
}
|
||||
#define arch_atomic64_fetch_add arch_atomic64_fetch_add
|
||||
|
||||
static __always_inline s64 arch_atomic64_fetch_sub(s64 i, atomic64_t *v)
|
||||
{
|
||||
return xadd(&v->counter, -i);
|
||||
}
|
||||
#define arch_atomic64_fetch_sub arch_atomic64_fetch_sub
|
||||
#define arch_atomic64_fetch_sub(i, v) arch_atomic64_fetch_add(-(i), v)
|
||||
|
||||
static __always_inline s64 arch_atomic64_cmpxchg(atomic64_t *v, s64 old, s64 new)
|
||||
{
|
||||
|
||||
@@ -3,103 +3,150 @@
|
||||
#define _ASM_X86_CMPXCHG_32_H
|
||||
|
||||
/*
|
||||
* Note: if you use set64_bit(), __cmpxchg64(), or their variants,
|
||||
* Note: if you use __cmpxchg64(), or their variants,
|
||||
* you need to test for the feature in boot_cpu_data.
|
||||
*/
|
||||
|
||||
union __u64_halves {
|
||||
u64 full;
|
||||
struct {
|
||||
u32 low, high;
|
||||
};
|
||||
};
|
||||
|
||||
#define __arch_cmpxchg64(_ptr, _old, _new, _lock) \
|
||||
({ \
|
||||
union __u64_halves o = { .full = (_old), }, \
|
||||
n = { .full = (_new), }; \
|
||||
\
|
||||
asm volatile(_lock "cmpxchg8b %[ptr]" \
|
||||
: [ptr] "+m" (*(_ptr)), \
|
||||
"+a" (o.low), "+d" (o.high) \
|
||||
: "b" (n.low), "c" (n.high) \
|
||||
: "memory"); \
|
||||
\
|
||||
o.full; \
|
||||
})
|
||||
|
||||
|
||||
static __always_inline u64 __cmpxchg64(volatile u64 *ptr, u64 old, u64 new)
|
||||
{
|
||||
return __arch_cmpxchg64(ptr, old, new, LOCK_PREFIX);
|
||||
}
|
||||
|
||||
static __always_inline u64 __cmpxchg64_local(volatile u64 *ptr, u64 old, u64 new)
|
||||
{
|
||||
return __arch_cmpxchg64(ptr, old, new,);
|
||||
}
|
||||
|
||||
#define __arch_try_cmpxchg64(_ptr, _oldp, _new, _lock) \
|
||||
({ \
|
||||
union __u64_halves o = { .full = *(_oldp), }, \
|
||||
n = { .full = (_new), }; \
|
||||
bool ret; \
|
||||
\
|
||||
asm volatile(_lock "cmpxchg8b %[ptr]" \
|
||||
CC_SET(e) \
|
||||
: CC_OUT(e) (ret), \
|
||||
[ptr] "+m" (*(_ptr)), \
|
||||
"+a" (o.low), "+d" (o.high) \
|
||||
: "b" (n.low), "c" (n.high) \
|
||||
: "memory"); \
|
||||
\
|
||||
if (unlikely(!ret)) \
|
||||
*(_oldp) = o.full; \
|
||||
\
|
||||
likely(ret); \
|
||||
})
|
||||
|
||||
static __always_inline bool __try_cmpxchg64(volatile u64 *ptr, u64 *oldp, u64 new)
|
||||
{
|
||||
return __arch_try_cmpxchg64(ptr, oldp, new, LOCK_PREFIX);
|
||||
}
|
||||
|
||||
static __always_inline bool __try_cmpxchg64_local(volatile u64 *ptr, u64 *oldp, u64 new)
|
||||
{
|
||||
return __arch_try_cmpxchg64(ptr, oldp, new,);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_X86_CMPXCHG64
|
||||
#define arch_cmpxchg64(ptr, o, n) \
|
||||
((__typeof__(*(ptr)))__cmpxchg64((ptr), (unsigned long long)(o), \
|
||||
(unsigned long long)(n)))
|
||||
#define arch_cmpxchg64_local(ptr, o, n) \
|
||||
((__typeof__(*(ptr)))__cmpxchg64_local((ptr), (unsigned long long)(o), \
|
||||
(unsigned long long)(n)))
|
||||
#define arch_try_cmpxchg64(ptr, po, n) \
|
||||
__try_cmpxchg64((ptr), (unsigned long long *)(po), \
|
||||
(unsigned long long)(n))
|
||||
#endif
|
||||
|
||||
static inline u64 __cmpxchg64(volatile u64 *ptr, u64 old, u64 new)
|
||||
{
|
||||
u64 prev;
|
||||
asm volatile(LOCK_PREFIX "cmpxchg8b %1"
|
||||
: "=A" (prev),
|
||||
"+m" (*ptr)
|
||||
: "b" ((u32)new),
|
||||
"c" ((u32)(new >> 32)),
|
||||
"0" (old)
|
||||
: "memory");
|
||||
return prev;
|
||||
}
|
||||
#define arch_cmpxchg64 __cmpxchg64
|
||||
|
||||
static inline u64 __cmpxchg64_local(volatile u64 *ptr, u64 old, u64 new)
|
||||
{
|
||||
u64 prev;
|
||||
asm volatile("cmpxchg8b %1"
|
||||
: "=A" (prev),
|
||||
"+m" (*ptr)
|
||||
: "b" ((u32)new),
|
||||
"c" ((u32)(new >> 32)),
|
||||
"0" (old)
|
||||
: "memory");
|
||||
return prev;
|
||||
}
|
||||
#define arch_cmpxchg64_local __cmpxchg64_local
|
||||
|
||||
static inline bool __try_cmpxchg64(volatile u64 *ptr, u64 *pold, u64 new)
|
||||
{
|
||||
bool success;
|
||||
u64 old = *pold;
|
||||
asm volatile(LOCK_PREFIX "cmpxchg8b %[ptr]"
|
||||
CC_SET(z)
|
||||
: CC_OUT(z) (success),
|
||||
[ptr] "+m" (*ptr),
|
||||
"+A" (old)
|
||||
: "b" ((u32)new),
|
||||
"c" ((u32)(new >> 32))
|
||||
: "memory");
|
||||
#define arch_try_cmpxchg64 __try_cmpxchg64
|
||||
|
||||
if (unlikely(!success))
|
||||
*pold = old;
|
||||
return success;
|
||||
}
|
||||
#define arch_try_cmpxchg64_local __try_cmpxchg64_local
|
||||
|
||||
#else
|
||||
|
||||
#ifndef CONFIG_X86_CMPXCHG64
|
||||
/*
|
||||
* Building a kernel capable running on 80386 and 80486. It may be necessary
|
||||
* to simulate the cmpxchg8b on the 80386 and 80486 CPU.
|
||||
*/
|
||||
|
||||
#define arch_cmpxchg64(ptr, o, n) \
|
||||
({ \
|
||||
__typeof__(*(ptr)) __ret; \
|
||||
__typeof__(*(ptr)) __old = (o); \
|
||||
__typeof__(*(ptr)) __new = (n); \
|
||||
alternative_io(LOCK_PREFIX_HERE \
|
||||
"call cmpxchg8b_emu", \
|
||||
"lock; cmpxchg8b (%%esi)" , \
|
||||
X86_FEATURE_CX8, \
|
||||
"=A" (__ret), \
|
||||
"S" ((ptr)), "0" (__old), \
|
||||
"b" ((unsigned int)__new), \
|
||||
"c" ((unsigned int)(__new>>32)) \
|
||||
: "memory"); \
|
||||
__ret; })
|
||||
#define __arch_cmpxchg64_emu(_ptr, _old, _new, _lock_loc, _lock) \
|
||||
({ \
|
||||
union __u64_halves o = { .full = (_old), }, \
|
||||
n = { .full = (_new), }; \
|
||||
\
|
||||
asm volatile(ALTERNATIVE(_lock_loc \
|
||||
"call cmpxchg8b_emu", \
|
||||
_lock "cmpxchg8b %[ptr]", X86_FEATURE_CX8) \
|
||||
: [ptr] "+m" (*(_ptr)), \
|
||||
"+a" (o.low), "+d" (o.high) \
|
||||
: "b" (n.low), "c" (n.high), "S" (_ptr) \
|
||||
: "memory"); \
|
||||
\
|
||||
o.full; \
|
||||
})
|
||||
|
||||
static __always_inline u64 arch_cmpxchg64(volatile u64 *ptr, u64 old, u64 new)
|
||||
{
|
||||
return __arch_cmpxchg64_emu(ptr, old, new, LOCK_PREFIX_HERE, "lock; ");
|
||||
}
|
||||
#define arch_cmpxchg64 arch_cmpxchg64
|
||||
|
||||
#define arch_cmpxchg64_local(ptr, o, n) \
|
||||
({ \
|
||||
__typeof__(*(ptr)) __ret; \
|
||||
__typeof__(*(ptr)) __old = (o); \
|
||||
__typeof__(*(ptr)) __new = (n); \
|
||||
alternative_io("call cmpxchg8b_emu", \
|
||||
"cmpxchg8b (%%esi)" , \
|
||||
X86_FEATURE_CX8, \
|
||||
"=A" (__ret), \
|
||||
"S" ((ptr)), "0" (__old), \
|
||||
"b" ((unsigned int)__new), \
|
||||
"c" ((unsigned int)(__new>>32)) \
|
||||
: "memory"); \
|
||||
__ret; })
|
||||
static __always_inline u64 arch_cmpxchg64_local(volatile u64 *ptr, u64 old, u64 new)
|
||||
{
|
||||
return __arch_cmpxchg64_emu(ptr, old, new, ,);
|
||||
}
|
||||
#define arch_cmpxchg64_local arch_cmpxchg64_local
|
||||
|
||||
#define __arch_try_cmpxchg64_emu(_ptr, _oldp, _new, _lock_loc, _lock) \
|
||||
({ \
|
||||
union __u64_halves o = { .full = *(_oldp), }, \
|
||||
n = { .full = (_new), }; \
|
||||
bool ret; \
|
||||
\
|
||||
asm volatile(ALTERNATIVE(_lock_loc \
|
||||
"call cmpxchg8b_emu", \
|
||||
_lock "cmpxchg8b %[ptr]", X86_FEATURE_CX8) \
|
||||
CC_SET(e) \
|
||||
: CC_OUT(e) (ret), \
|
||||
[ptr] "+m" (*(_ptr)), \
|
||||
"+a" (o.low), "+d" (o.high) \
|
||||
: "b" (n.low), "c" (n.high), "S" (_ptr) \
|
||||
: "memory"); \
|
||||
\
|
||||
if (unlikely(!ret)) \
|
||||
*(_oldp) = o.full; \
|
||||
\
|
||||
likely(ret); \
|
||||
})
|
||||
|
||||
static __always_inline bool arch_try_cmpxchg64(volatile u64 *ptr, u64 *oldp, u64 new)
|
||||
{
|
||||
return __arch_try_cmpxchg64_emu(ptr, oldp, new, LOCK_PREFIX_HERE, "lock; ");
|
||||
}
|
||||
#define arch_try_cmpxchg64 arch_try_cmpxchg64
|
||||
|
||||
static __always_inline bool arch_try_cmpxchg64_local(volatile u64 *ptr, u64 *oldp, u64 new)
|
||||
{
|
||||
return __arch_try_cmpxchg64_emu(ptr, oldp, new, ,);
|
||||
}
|
||||
#define arch_try_cmpxchg64_local arch_try_cmpxchg64_local
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -20,6 +20,12 @@
|
||||
arch_try_cmpxchg((ptr), (po), (n)); \
|
||||
})
|
||||
|
||||
#define arch_try_cmpxchg64_local(ptr, po, n) \
|
||||
({ \
|
||||
BUILD_BUG_ON(sizeof(*(ptr)) != 8); \
|
||||
arch_try_cmpxchg_local((ptr), (po), (n)); \
|
||||
})
|
||||
|
||||
union __u128_halves {
|
||||
u128 full;
|
||||
struct {
|
||||
@@ -62,7 +68,7 @@ static __always_inline u128 arch_cmpxchg128_local(volatile u128 *ptr, u128 old,
|
||||
asm volatile(_lock "cmpxchg16b %[ptr]" \
|
||||
CC_SET(e) \
|
||||
: CC_OUT(e) (ret), \
|
||||
[ptr] "+m" (*ptr), \
|
||||
[ptr] "+m" (*(_ptr)), \
|
||||
"+a" (o.low), "+d" (o.high) \
|
||||
: "b" (n.low), "c" (n.high) \
|
||||
: "memory"); \
|
||||
|
||||
@@ -85,6 +85,8 @@ DECLARE_STATIC_KEY_TRUE(virt_spin_lock_key);
|
||||
#define virt_spin_lock virt_spin_lock
|
||||
static inline bool virt_spin_lock(struct qspinlock *lock)
|
||||
{
|
||||
int val;
|
||||
|
||||
if (!static_branch_likely(&virt_spin_lock_key))
|
||||
return false;
|
||||
|
||||
@@ -94,10 +96,13 @@ static inline bool virt_spin_lock(struct qspinlock *lock)
|
||||
* horrible lock 'holder' preemption issues.
|
||||
*/
|
||||
|
||||
do {
|
||||
while (atomic_read(&lock->val) != 0)
|
||||
cpu_relax();
|
||||
} while (atomic_cmpxchg(&lock->val, 0, _Q_LOCKED_VAL) != 0);
|
||||
__retry:
|
||||
val = atomic_read(&lock->val);
|
||||
|
||||
if (val || !atomic_try_cmpxchg(&lock->val, &val, _Q_LOCKED_VAL)) {
|
||||
cpu_relax();
|
||||
goto __retry;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -25,9 +25,9 @@ __PV_CALLEE_SAVE_REGS_THUNK(__pv_queued_spin_unlock_slowpath, ".spinlock.text");
|
||||
*
|
||||
* void __lockfunc __pv_queued_spin_unlock(struct qspinlock *lock)
|
||||
* {
|
||||
* u8 lockval = cmpxchg(&lock->locked, _Q_LOCKED_VAL, 0);
|
||||
* u8 lockval = _Q_LOCKED_VAL;
|
||||
*
|
||||
* if (likely(lockval == _Q_LOCKED_VAL))
|
||||
* if (try_cmpxchg(&lock->locked, &lockval, 0))
|
||||
* return;
|
||||
* pv_queued_spin_unlock_slowpath(lock, lockval);
|
||||
* }
|
||||
@@ -40,10 +40,9 @@ __PV_CALLEE_SAVE_REGS_THUNK(__pv_queued_spin_unlock_slowpath, ".spinlock.text");
|
||||
#define PV_UNLOCK_ASM \
|
||||
FRAME_BEGIN \
|
||||
"push %rdx\n\t" \
|
||||
"mov $0x1,%eax\n\t" \
|
||||
"mov $" __stringify(_Q_LOCKED_VAL) ",%eax\n\t" \
|
||||
"xor %edx,%edx\n\t" \
|
||||
LOCK_PREFIX "cmpxchg %dl,(%rdi)\n\t" \
|
||||
"cmp $0x1,%al\n\t" \
|
||||
"jne .slowpath\n\t" \
|
||||
"pop %rdx\n\t" \
|
||||
FRAME_END \
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
#include <asm/svm.h>
|
||||
#include <asm/e820/api.h>
|
||||
|
||||
DEFINE_STATIC_KEY_FALSE(kvm_async_pf_enabled);
|
||||
DEFINE_STATIC_KEY_FALSE_RO(kvm_async_pf_enabled);
|
||||
|
||||
static int kvmapf = 1;
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ EXPORT_SYMBOL(tsc_khz);
|
||||
static int __read_mostly tsc_unstable;
|
||||
static unsigned int __initdata tsc_early_khz;
|
||||
|
||||
static DEFINE_STATIC_KEY_FALSE(__use_tsc);
|
||||
static DEFINE_STATIC_KEY_FALSE_RO(__use_tsc);
|
||||
|
||||
int tsc_clocksource_reliable;
|
||||
|
||||
|
||||
@@ -124,7 +124,6 @@ static struct coreboot_driver cbmem_entry_driver = {
|
||||
.probe = cbmem_entry_probe,
|
||||
.drv = {
|
||||
.name = "cbmem",
|
||||
.owner = THIS_MODULE,
|
||||
.dev_groups = dev_groups,
|
||||
},
|
||||
.id_table = cbmem_ids,
|
||||
|
||||
@@ -85,13 +85,15 @@ static void coreboot_device_release(struct device *dev)
|
||||
kfree(device);
|
||||
}
|
||||
|
||||
int coreboot_driver_register(struct coreboot_driver *driver)
|
||||
int __coreboot_driver_register(struct coreboot_driver *driver,
|
||||
struct module *owner)
|
||||
{
|
||||
driver->drv.bus = &coreboot_bus_type;
|
||||
driver->drv.owner = owner;
|
||||
|
||||
return driver_register(&driver->drv);
|
||||
}
|
||||
EXPORT_SYMBOL(coreboot_driver_register);
|
||||
EXPORT_SYMBOL(__coreboot_driver_register);
|
||||
|
||||
void coreboot_driver_unregister(struct coreboot_driver *driver)
|
||||
{
|
||||
|
||||
@@ -97,8 +97,12 @@ struct coreboot_driver {
|
||||
const struct coreboot_device_id *id_table;
|
||||
};
|
||||
|
||||
/* use a macro to avoid include chaining to get THIS_MODULE */
|
||||
#define coreboot_driver_register(driver) \
|
||||
__coreboot_driver_register(driver, THIS_MODULE)
|
||||
/* Register a driver that uses the data from a coreboot table. */
|
||||
int coreboot_driver_register(struct coreboot_driver *driver);
|
||||
int __coreboot_driver_register(struct coreboot_driver *driver,
|
||||
struct module *owner);
|
||||
|
||||
/* Unregister a driver that uses the data from a coreboot table. */
|
||||
void coreboot_driver_unregister(struct coreboot_driver *driver);
|
||||
|
||||
@@ -180,6 +180,11 @@ static inline bool is_kernel_rodata(unsigned long addr)
|
||||
addr < (unsigned long)__end_rodata;
|
||||
}
|
||||
|
||||
static inline bool is_kernel_ro_after_init(unsigned long addr)
|
||||
{
|
||||
return addr >= (unsigned long)__start_ro_after_init &&
|
||||
addr < (unsigned long)__end_ro_after_init;
|
||||
}
|
||||
/**
|
||||
* is_kernel_inittext - checks if the pointer address is located in the
|
||||
* .init.text section
|
||||
|
||||
@@ -216,6 +216,7 @@ extern struct jump_entry __start___jump_table[];
|
||||
extern struct jump_entry __stop___jump_table[];
|
||||
|
||||
extern void jump_label_init(void);
|
||||
extern void jump_label_init_ro(void);
|
||||
extern void jump_label_lock(void);
|
||||
extern void jump_label_unlock(void);
|
||||
extern void arch_jump_label_transform(struct jump_entry *entry,
|
||||
@@ -265,6 +266,8 @@ static __always_inline void jump_label_init(void)
|
||||
static_key_initialized = true;
|
||||
}
|
||||
|
||||
static __always_inline void jump_label_init_ro(void) { }
|
||||
|
||||
static __always_inline bool static_key_false(struct static_key *key)
|
||||
{
|
||||
if (unlikely_notrace(static_key_count(key) > 0))
|
||||
|
||||
+17
-20
@@ -809,11 +809,8 @@ struct perf_event {
|
||||
u64 (*clock)(void);
|
||||
perf_overflow_handler_t overflow_handler;
|
||||
void *overflow_handler_context;
|
||||
#ifdef CONFIG_BPF_SYSCALL
|
||||
perf_overflow_handler_t orig_overflow_handler;
|
||||
struct bpf_prog *prog;
|
||||
u64 bpf_cookie;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_EVENT_TRACING
|
||||
struct trace_event_call *tp_event;
|
||||
@@ -883,6 +880,7 @@ struct perf_event_pmu_context {
|
||||
|
||||
unsigned int nr_events;
|
||||
unsigned int nr_cgroups;
|
||||
unsigned int nr_freq;
|
||||
|
||||
atomic_t refcount; /* event <-> epc */
|
||||
struct rcu_head rcu_head;
|
||||
@@ -897,6 +895,11 @@ struct perf_event_pmu_context {
|
||||
int rotate_necessary;
|
||||
};
|
||||
|
||||
static inline bool perf_pmu_ctx_is_active(struct perf_event_pmu_context *epc)
|
||||
{
|
||||
return !list_empty(&epc->flexible_active) || !list_empty(&epc->pinned_active);
|
||||
}
|
||||
|
||||
struct perf_event_groups {
|
||||
struct rb_root tree;
|
||||
u64 index;
|
||||
@@ -1342,8 +1345,10 @@ extern int perf_event_output(struct perf_event *event,
|
||||
struct pt_regs *regs);
|
||||
|
||||
static inline bool
|
||||
__is_default_overflow_handler(perf_overflow_handler_t overflow_handler)
|
||||
is_default_overflow_handler(struct perf_event *event)
|
||||
{
|
||||
perf_overflow_handler_t overflow_handler = event->overflow_handler;
|
||||
|
||||
if (likely(overflow_handler == perf_event_output_forward))
|
||||
return true;
|
||||
if (unlikely(overflow_handler == perf_event_output_backward))
|
||||
@@ -1351,22 +1356,6 @@ __is_default_overflow_handler(perf_overflow_handler_t overflow_handler)
|
||||
return false;
|
||||
}
|
||||
|
||||
#define is_default_overflow_handler(event) \
|
||||
__is_default_overflow_handler((event)->overflow_handler)
|
||||
|
||||
#ifdef CONFIG_BPF_SYSCALL
|
||||
static inline bool uses_default_overflow_handler(struct perf_event *event)
|
||||
{
|
||||
if (likely(is_default_overflow_handler(event)))
|
||||
return true;
|
||||
|
||||
return __is_default_overflow_handler(event->orig_overflow_handler);
|
||||
}
|
||||
#else
|
||||
#define uses_default_overflow_handler(event) \
|
||||
is_default_overflow_handler(event)
|
||||
#endif
|
||||
|
||||
extern void
|
||||
perf_event_header__init_id(struct perf_event_header *header,
|
||||
struct perf_sample_data *data,
|
||||
@@ -1715,6 +1704,14 @@ perf_event_addr_filters(struct perf_event *event)
|
||||
return ifh;
|
||||
}
|
||||
|
||||
static inline struct fasync_struct **perf_event_fasync(struct perf_event *event)
|
||||
{
|
||||
/* Only the parent has fasync state */
|
||||
if (event->parent)
|
||||
event = event->parent;
|
||||
return &event->fasync;
|
||||
}
|
||||
|
||||
extern void perf_event_addr_filters_sync(struct perf_event *event);
|
||||
extern void perf_report_aux_output_id(struct perf_event *event, u64 hw_id);
|
||||
|
||||
|
||||
@@ -1415,6 +1415,7 @@ static void mark_readonly(void)
|
||||
* insecure pages which are W+X.
|
||||
*/
|
||||
flush_module_init_free_work();
|
||||
jump_label_init_ro();
|
||||
mark_rodata_ro();
|
||||
debug_checkwx();
|
||||
rodata_test();
|
||||
|
||||
@@ -432,7 +432,7 @@ static __always_inline void ct_kernel_enter(bool user, int offset) { }
|
||||
#define CREATE_TRACE_POINTS
|
||||
#include <trace/events/context_tracking.h>
|
||||
|
||||
DEFINE_STATIC_KEY_FALSE(context_tracking_key);
|
||||
DEFINE_STATIC_KEY_FALSE_RO(context_tracking_key);
|
||||
EXPORT_SYMBOL_GPL(context_tracking_key);
|
||||
|
||||
static noinstr bool context_tracking_recursion_enter(void)
|
||||
|
||||
+144
-129
@@ -2302,8 +2302,10 @@ event_sched_out(struct perf_event *event, struct perf_event_context *ctx)
|
||||
|
||||
if (!is_software_event(event))
|
||||
cpc->active_oncpu--;
|
||||
if (event->attr.freq && event->attr.sample_freq)
|
||||
if (event->attr.freq && event->attr.sample_freq) {
|
||||
ctx->nr_freq--;
|
||||
epc->nr_freq--;
|
||||
}
|
||||
if (event->attr.exclusive || !cpc->active_oncpu)
|
||||
cpc->exclusive = 0;
|
||||
|
||||
@@ -2558,9 +2560,10 @@ event_sched_in(struct perf_event *event, struct perf_event_context *ctx)
|
||||
|
||||
if (!is_software_event(event))
|
||||
cpc->active_oncpu++;
|
||||
if (event->attr.freq && event->attr.sample_freq)
|
||||
if (event->attr.freq && event->attr.sample_freq) {
|
||||
ctx->nr_freq++;
|
||||
|
||||
epc->nr_freq++;
|
||||
}
|
||||
if (event->attr.exclusive)
|
||||
cpc->exclusive = 1;
|
||||
|
||||
@@ -4123,30 +4126,14 @@ static void perf_adjust_period(struct perf_event *event, u64 nsec, u64 count, bo
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* combine freq adjustment with unthrottling to avoid two passes over the
|
||||
* events. At the same time, make sure, having freq events does not change
|
||||
* the rate of unthrottling as that would introduce bias.
|
||||
*/
|
||||
static void
|
||||
perf_adjust_freq_unthr_context(struct perf_event_context *ctx, bool unthrottle)
|
||||
static void perf_adjust_freq_unthr_events(struct list_head *event_list)
|
||||
{
|
||||
struct perf_event *event;
|
||||
struct hw_perf_event *hwc;
|
||||
u64 now, period = TICK_NSEC;
|
||||
s64 delta;
|
||||
|
||||
/*
|
||||
* only need to iterate over all events iff:
|
||||
* - context have events in frequency mode (needs freq adjust)
|
||||
* - there are events to unthrottle on this cpu
|
||||
*/
|
||||
if (!(ctx->nr_freq || unthrottle))
|
||||
return;
|
||||
|
||||
raw_spin_lock(&ctx->lock);
|
||||
|
||||
list_for_each_entry_rcu(event, &ctx->event_list, event_entry) {
|
||||
list_for_each_entry(event, event_list, active_list) {
|
||||
if (event->state != PERF_EVENT_STATE_ACTIVE)
|
||||
continue;
|
||||
|
||||
@@ -4154,18 +4141,17 @@ perf_adjust_freq_unthr_context(struct perf_event_context *ctx, bool unthrottle)
|
||||
if (!event_filter_match(event))
|
||||
continue;
|
||||
|
||||
perf_pmu_disable(event->pmu);
|
||||
|
||||
hwc = &event->hw;
|
||||
|
||||
if (hwc->interrupts == MAX_INTERRUPTS) {
|
||||
hwc->interrupts = 0;
|
||||
perf_log_throttle(event, 1);
|
||||
event->pmu->start(event, 0);
|
||||
if (!event->attr.freq || !event->attr.sample_freq)
|
||||
event->pmu->start(event, 0);
|
||||
}
|
||||
|
||||
if (!event->attr.freq || !event->attr.sample_freq)
|
||||
goto next;
|
||||
continue;
|
||||
|
||||
/*
|
||||
* stop the event and update event->count
|
||||
@@ -4187,8 +4173,41 @@ perf_adjust_freq_unthr_context(struct perf_event_context *ctx, bool unthrottle)
|
||||
perf_adjust_period(event, period, delta, false);
|
||||
|
||||
event->pmu->start(event, delta > 0 ? PERF_EF_RELOAD : 0);
|
||||
next:
|
||||
perf_pmu_enable(event->pmu);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* combine freq adjustment with unthrottling to avoid two passes over the
|
||||
* events. At the same time, make sure, having freq events does not change
|
||||
* the rate of unthrottling as that would introduce bias.
|
||||
*/
|
||||
static void
|
||||
perf_adjust_freq_unthr_context(struct perf_event_context *ctx, bool unthrottle)
|
||||
{
|
||||
struct perf_event_pmu_context *pmu_ctx;
|
||||
|
||||
/*
|
||||
* only need to iterate over all events iff:
|
||||
* - context have events in frequency mode (needs freq adjust)
|
||||
* - there are events to unthrottle on this cpu
|
||||
*/
|
||||
if (!(ctx->nr_freq || unthrottle))
|
||||
return;
|
||||
|
||||
raw_spin_lock(&ctx->lock);
|
||||
|
||||
list_for_each_entry(pmu_ctx, &ctx->pmu_ctx_list, pmu_ctx_entry) {
|
||||
if (!(pmu_ctx->nr_freq || unthrottle))
|
||||
continue;
|
||||
if (!perf_pmu_ctx_is_active(pmu_ctx))
|
||||
continue;
|
||||
if (pmu_ctx->pmu->capabilities & PERF_PMU_CAP_NO_INTERRUPT)
|
||||
continue;
|
||||
|
||||
perf_pmu_disable(pmu_ctx->pmu);
|
||||
perf_adjust_freq_unthr_events(&pmu_ctx->pinned_active);
|
||||
perf_adjust_freq_unthr_events(&pmu_ctx->flexible_active);
|
||||
perf_pmu_enable(pmu_ctx->pmu);
|
||||
}
|
||||
|
||||
raw_spin_unlock(&ctx->lock);
|
||||
@@ -6685,14 +6704,6 @@ static const struct file_operations perf_fops = {
|
||||
* to user-space before waking everybody up.
|
||||
*/
|
||||
|
||||
static inline struct fasync_struct **perf_event_fasync(struct perf_event *event)
|
||||
{
|
||||
/* only the parent has fasync state */
|
||||
if (event->parent)
|
||||
event = event->parent;
|
||||
return &event->fasync;
|
||||
}
|
||||
|
||||
void perf_event_wakeup(struct perf_event *event)
|
||||
{
|
||||
ring_buffer_wakeup(event);
|
||||
@@ -9530,6 +9541,100 @@ static inline bool sample_is_allowed(struct perf_event *event, struct pt_regs *r
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_BPF_SYSCALL
|
||||
static int bpf_overflow_handler(struct perf_event *event,
|
||||
struct perf_sample_data *data,
|
||||
struct pt_regs *regs)
|
||||
{
|
||||
struct bpf_perf_event_data_kern ctx = {
|
||||
.data = data,
|
||||
.event = event,
|
||||
};
|
||||
struct bpf_prog *prog;
|
||||
int ret = 0;
|
||||
|
||||
ctx.regs = perf_arch_bpf_user_pt_regs(regs);
|
||||
if (unlikely(__this_cpu_inc_return(bpf_prog_active) != 1))
|
||||
goto out;
|
||||
rcu_read_lock();
|
||||
prog = READ_ONCE(event->prog);
|
||||
if (prog) {
|
||||
perf_prepare_sample(data, event, regs);
|
||||
ret = bpf_prog_run(prog, &ctx);
|
||||
}
|
||||
rcu_read_unlock();
|
||||
out:
|
||||
__this_cpu_dec(bpf_prog_active);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int perf_event_set_bpf_handler(struct perf_event *event,
|
||||
struct bpf_prog *prog,
|
||||
u64 bpf_cookie)
|
||||
{
|
||||
if (event->overflow_handler_context)
|
||||
/* hw breakpoint or kernel counter */
|
||||
return -EINVAL;
|
||||
|
||||
if (event->prog)
|
||||
return -EEXIST;
|
||||
|
||||
if (prog->type != BPF_PROG_TYPE_PERF_EVENT)
|
||||
return -EINVAL;
|
||||
|
||||
if (event->attr.precise_ip &&
|
||||
prog->call_get_stack &&
|
||||
(!(event->attr.sample_type & PERF_SAMPLE_CALLCHAIN) ||
|
||||
event->attr.exclude_callchain_kernel ||
|
||||
event->attr.exclude_callchain_user)) {
|
||||
/*
|
||||
* On perf_event with precise_ip, calling bpf_get_stack()
|
||||
* may trigger unwinder warnings and occasional crashes.
|
||||
* bpf_get_[stack|stackid] works around this issue by using
|
||||
* callchain attached to perf_sample_data. If the
|
||||
* perf_event does not full (kernel and user) callchain
|
||||
* attached to perf_sample_data, do not allow attaching BPF
|
||||
* program that calls bpf_get_[stack|stackid].
|
||||
*/
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
event->prog = prog;
|
||||
event->bpf_cookie = bpf_cookie;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void perf_event_free_bpf_handler(struct perf_event *event)
|
||||
{
|
||||
struct bpf_prog *prog = event->prog;
|
||||
|
||||
if (!prog)
|
||||
return;
|
||||
|
||||
event->prog = NULL;
|
||||
bpf_prog_put(prog);
|
||||
}
|
||||
#else
|
||||
static inline int bpf_overflow_handler(struct perf_event *event,
|
||||
struct perf_sample_data *data,
|
||||
struct pt_regs *regs)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline int perf_event_set_bpf_handler(struct perf_event *event,
|
||||
struct bpf_prog *prog,
|
||||
u64 bpf_cookie)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static inline void perf_event_free_bpf_handler(struct perf_event *event)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Generic event overflow handling, sampling.
|
||||
*/
|
||||
@@ -9550,6 +9655,9 @@ static int __perf_event_overflow(struct perf_event *event,
|
||||
|
||||
ret = __perf_event_account_interrupt(event, throttle);
|
||||
|
||||
if (event->prog && !bpf_overflow_handler(event, data, regs))
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* XXX event_limit might not quite work as expected on inherited
|
||||
* events
|
||||
@@ -10408,97 +10516,6 @@ static void perf_event_free_filter(struct perf_event *event)
|
||||
ftrace_profile_free_filter(event);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_BPF_SYSCALL
|
||||
static void bpf_overflow_handler(struct perf_event *event,
|
||||
struct perf_sample_data *data,
|
||||
struct pt_regs *regs)
|
||||
{
|
||||
struct bpf_perf_event_data_kern ctx = {
|
||||
.data = data,
|
||||
.event = event,
|
||||
};
|
||||
struct bpf_prog *prog;
|
||||
int ret = 0;
|
||||
|
||||
ctx.regs = perf_arch_bpf_user_pt_regs(regs);
|
||||
if (unlikely(__this_cpu_inc_return(bpf_prog_active) != 1))
|
||||
goto out;
|
||||
rcu_read_lock();
|
||||
prog = READ_ONCE(event->prog);
|
||||
if (prog) {
|
||||
perf_prepare_sample(data, event, regs);
|
||||
ret = bpf_prog_run(prog, &ctx);
|
||||
}
|
||||
rcu_read_unlock();
|
||||
out:
|
||||
__this_cpu_dec(bpf_prog_active);
|
||||
if (!ret)
|
||||
return;
|
||||
|
||||
event->orig_overflow_handler(event, data, regs);
|
||||
}
|
||||
|
||||
static int perf_event_set_bpf_handler(struct perf_event *event,
|
||||
struct bpf_prog *prog,
|
||||
u64 bpf_cookie)
|
||||
{
|
||||
if (event->overflow_handler_context)
|
||||
/* hw breakpoint or kernel counter */
|
||||
return -EINVAL;
|
||||
|
||||
if (event->prog)
|
||||
return -EEXIST;
|
||||
|
||||
if (prog->type != BPF_PROG_TYPE_PERF_EVENT)
|
||||
return -EINVAL;
|
||||
|
||||
if (event->attr.precise_ip &&
|
||||
prog->call_get_stack &&
|
||||
(!(event->attr.sample_type & PERF_SAMPLE_CALLCHAIN) ||
|
||||
event->attr.exclude_callchain_kernel ||
|
||||
event->attr.exclude_callchain_user)) {
|
||||
/*
|
||||
* On perf_event with precise_ip, calling bpf_get_stack()
|
||||
* may trigger unwinder warnings and occasional crashes.
|
||||
* bpf_get_[stack|stackid] works around this issue by using
|
||||
* callchain attached to perf_sample_data. If the
|
||||
* perf_event does not full (kernel and user) callchain
|
||||
* attached to perf_sample_data, do not allow attaching BPF
|
||||
* program that calls bpf_get_[stack|stackid].
|
||||
*/
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
event->prog = prog;
|
||||
event->bpf_cookie = bpf_cookie;
|
||||
event->orig_overflow_handler = READ_ONCE(event->overflow_handler);
|
||||
WRITE_ONCE(event->overflow_handler, bpf_overflow_handler);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void perf_event_free_bpf_handler(struct perf_event *event)
|
||||
{
|
||||
struct bpf_prog *prog = event->prog;
|
||||
|
||||
if (!prog)
|
||||
return;
|
||||
|
||||
WRITE_ONCE(event->overflow_handler, event->orig_overflow_handler);
|
||||
event->prog = NULL;
|
||||
bpf_prog_put(prog);
|
||||
}
|
||||
#else
|
||||
static int perf_event_set_bpf_handler(struct perf_event *event,
|
||||
struct bpf_prog *prog,
|
||||
u64 bpf_cookie)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
static void perf_event_free_bpf_handler(struct perf_event *event)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* returns true if the event is a tracepoint, or a kprobe/upprobe created
|
||||
* with perf_event_open()
|
||||
@@ -11957,13 +11974,11 @@ perf_event_alloc(struct perf_event_attr *attr, int cpu,
|
||||
overflow_handler = parent_event->overflow_handler;
|
||||
context = parent_event->overflow_handler_context;
|
||||
#if defined(CONFIG_BPF_SYSCALL) && defined(CONFIG_EVENT_TRACING)
|
||||
if (overflow_handler == bpf_overflow_handler) {
|
||||
if (parent_event->prog) {
|
||||
struct bpf_prog *prog = parent_event->prog;
|
||||
|
||||
bpf_prog_inc(prog);
|
||||
event->prog = prog;
|
||||
event->orig_overflow_handler =
|
||||
parent_event->orig_overflow_handler;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ static void perf_output_wakeup(struct perf_output_handle *handle)
|
||||
atomic_set(&handle->rb->poll, EPOLLIN);
|
||||
|
||||
handle->event->pending_wakeup = 1;
|
||||
|
||||
if (*perf_event_fasync(handle->event) && !handle->event->pending_kill)
|
||||
handle->event->pending_kill = POLL_IN;
|
||||
|
||||
irq_work_queue(&handle->event->pending_irq);
|
||||
}
|
||||
|
||||
|
||||
@@ -530,6 +530,45 @@ void __init jump_label_init(void)
|
||||
cpus_read_unlock();
|
||||
}
|
||||
|
||||
static inline bool static_key_sealed(struct static_key *key)
|
||||
{
|
||||
return (key->type & JUMP_TYPE_LINKED) && !(key->type & ~JUMP_TYPE_MASK);
|
||||
}
|
||||
|
||||
static inline void static_key_seal(struct static_key *key)
|
||||
{
|
||||
unsigned long type = key->type & JUMP_TYPE_TRUE;
|
||||
key->type = JUMP_TYPE_LINKED | type;
|
||||
}
|
||||
|
||||
void jump_label_init_ro(void)
|
||||
{
|
||||
struct jump_entry *iter_start = __start___jump_table;
|
||||
struct jump_entry *iter_stop = __stop___jump_table;
|
||||
struct jump_entry *iter;
|
||||
|
||||
if (WARN_ON_ONCE(!static_key_initialized))
|
||||
return;
|
||||
|
||||
cpus_read_lock();
|
||||
jump_label_lock();
|
||||
|
||||
for (iter = iter_start; iter < iter_stop; iter++) {
|
||||
struct static_key *iterk = jump_entry_key(iter);
|
||||
|
||||
if (!is_kernel_ro_after_init((unsigned long)iterk))
|
||||
continue;
|
||||
|
||||
if (static_key_sealed(iterk))
|
||||
continue;
|
||||
|
||||
static_key_seal(iterk);
|
||||
}
|
||||
|
||||
jump_label_unlock();
|
||||
cpus_read_unlock();
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MODULES
|
||||
|
||||
enum jump_label_type jump_label_init_type(struct jump_entry *entry)
|
||||
@@ -650,6 +689,15 @@ static int jump_label_add_module(struct module *mod)
|
||||
static_key_set_entries(key, iter);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the key was sealed at init, then there's no need to keep a
|
||||
* reference to its module entries - just patch them now and be
|
||||
* done with it.
|
||||
*/
|
||||
if (static_key_sealed(key))
|
||||
goto do_poke;
|
||||
|
||||
jlm = kzalloc(sizeof(struct static_key_mod), GFP_KERNEL);
|
||||
if (!jlm)
|
||||
return -ENOMEM;
|
||||
@@ -675,6 +723,7 @@ static int jump_label_add_module(struct module *mod)
|
||||
static_key_set_linked(key);
|
||||
|
||||
/* Only update if we've changed from our initial state */
|
||||
do_poke:
|
||||
if (jump_label_type(iter) != jump_label_init_type(iter))
|
||||
__jump_label_update(key, iter, iter_stop, true);
|
||||
}
|
||||
@@ -699,6 +748,10 @@ static void jump_label_del_module(struct module *mod)
|
||||
if (within_module((unsigned long)key, mod))
|
||||
continue;
|
||||
|
||||
/* No @jlm allocated because key was sealed at init. */
|
||||
if (static_key_sealed(key))
|
||||
continue;
|
||||
|
||||
/* No memory during module load */
|
||||
if (WARN_ON(!static_key_linked(key)))
|
||||
continue;
|
||||
|
||||
@@ -53,8 +53,8 @@ static inline void __lockevent_add(enum lock_events event, int inc)
|
||||
#else /* CONFIG_LOCK_EVENT_COUNTS */
|
||||
|
||||
#define lockevent_inc(ev)
|
||||
#define lockevent_add(ev, c)
|
||||
#define lockevent_cond_inc(ev, c)
|
||||
#define lockevent_add(ev, c) do { (void)(c); } while (0)
|
||||
#define lockevent_cond_inc(ev, c) do { (void)(c); } while (0)
|
||||
|
||||
#endif /* CONFIG_LOCK_EVENT_COUNTS */
|
||||
|
||||
|
||||
@@ -220,21 +220,18 @@ static __always_inline void clear_pending_set_locked(struct qspinlock *lock)
|
||||
*/
|
||||
static __always_inline u32 xchg_tail(struct qspinlock *lock, u32 tail)
|
||||
{
|
||||
u32 old, new, val = atomic_read(&lock->val);
|
||||
u32 old, new;
|
||||
|
||||
for (;;) {
|
||||
new = (val & _Q_LOCKED_PENDING_MASK) | tail;
|
||||
old = atomic_read(&lock->val);
|
||||
do {
|
||||
new = (old & _Q_LOCKED_PENDING_MASK) | tail;
|
||||
/*
|
||||
* We can use relaxed semantics since the caller ensures that
|
||||
* the MCS node is properly initialized before updating the
|
||||
* tail.
|
||||
*/
|
||||
old = atomic_cmpxchg_relaxed(&lock->val, val, new);
|
||||
if (old == val)
|
||||
break;
|
||||
} while (!atomic_try_cmpxchg_relaxed(&lock->val, &old, new));
|
||||
|
||||
val = old;
|
||||
}
|
||||
return old;
|
||||
}
|
||||
#endif /* _Q_PENDING_BITS == 8 */
|
||||
|
||||
@@ -86,9 +86,10 @@ static inline bool pv_hybrid_queued_unfair_trylock(struct qspinlock *lock)
|
||||
*/
|
||||
for (;;) {
|
||||
int val = atomic_read(&lock->val);
|
||||
u8 old = 0;
|
||||
|
||||
if (!(val & _Q_LOCKED_PENDING_MASK) &&
|
||||
(cmpxchg_acquire(&lock->locked, 0, _Q_LOCKED_VAL) == 0)) {
|
||||
try_cmpxchg_acquire(&lock->locked, &old, _Q_LOCKED_VAL)) {
|
||||
lockevent_inc(pv_lock_stealing);
|
||||
return true;
|
||||
}
|
||||
@@ -116,11 +117,12 @@ static __always_inline void set_pending(struct qspinlock *lock)
|
||||
* barrier. Therefore, an atomic cmpxchg_acquire() is used to acquire the
|
||||
* lock just to be sure that it will get it.
|
||||
*/
|
||||
static __always_inline int trylock_clear_pending(struct qspinlock *lock)
|
||||
static __always_inline bool trylock_clear_pending(struct qspinlock *lock)
|
||||
{
|
||||
u16 old = _Q_PENDING_VAL;
|
||||
|
||||
return !READ_ONCE(lock->locked) &&
|
||||
(cmpxchg_acquire(&lock->locked_pending, _Q_PENDING_VAL,
|
||||
_Q_LOCKED_VAL) == _Q_PENDING_VAL);
|
||||
try_cmpxchg_acquire(&lock->locked_pending, &old, _Q_LOCKED_VAL);
|
||||
}
|
||||
#else /* _Q_PENDING_BITS == 8 */
|
||||
static __always_inline void set_pending(struct qspinlock *lock)
|
||||
@@ -128,27 +130,21 @@ static __always_inline void set_pending(struct qspinlock *lock)
|
||||
atomic_or(_Q_PENDING_VAL, &lock->val);
|
||||
}
|
||||
|
||||
static __always_inline int trylock_clear_pending(struct qspinlock *lock)
|
||||
static __always_inline bool trylock_clear_pending(struct qspinlock *lock)
|
||||
{
|
||||
int val = atomic_read(&lock->val);
|
||||
|
||||
for (;;) {
|
||||
int old, new;
|
||||
|
||||
if (val & _Q_LOCKED_MASK)
|
||||
break;
|
||||
int old, new;
|
||||
|
||||
old = atomic_read(&lock->val);
|
||||
do {
|
||||
if (old & _Q_LOCKED_MASK)
|
||||
return false;
|
||||
/*
|
||||
* Try to clear pending bit & set locked bit
|
||||
*/
|
||||
old = val;
|
||||
new = (val & ~_Q_PENDING_MASK) | _Q_LOCKED_VAL;
|
||||
val = atomic_cmpxchg_acquire(&lock->val, old, new);
|
||||
new = (old & ~_Q_PENDING_MASK) | _Q_LOCKED_VAL;
|
||||
} while (!atomic_try_cmpxchg_acquire (&lock->val, &old, new));
|
||||
|
||||
if (val == old)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
return true;
|
||||
}
|
||||
#endif /* _Q_PENDING_BITS == 8 */
|
||||
|
||||
@@ -216,8 +212,9 @@ static struct qspinlock **pv_hash(struct qspinlock *lock, struct pv_node *node)
|
||||
int hopcnt = 0;
|
||||
|
||||
for_each_hash_entry(he, offset, hash) {
|
||||
struct qspinlock *old = NULL;
|
||||
hopcnt++;
|
||||
if (!cmpxchg(&he->lock, NULL, lock)) {
|
||||
if (try_cmpxchg(&he->lock, &old, lock)) {
|
||||
WRITE_ONCE(he->node, node);
|
||||
lockevent_pv_hop(hopcnt);
|
||||
return &he->lock;
|
||||
@@ -294,7 +291,7 @@ static void pv_wait_node(struct mcs_spinlock *node, struct mcs_spinlock *prev)
|
||||
{
|
||||
struct pv_node *pn = (struct pv_node *)node;
|
||||
struct pv_node *pp = (struct pv_node *)prev;
|
||||
bool __maybe_unused wait_early;
|
||||
bool wait_early;
|
||||
int loop;
|
||||
|
||||
for (;;) {
|
||||
@@ -360,7 +357,7 @@ static void pv_wait_node(struct mcs_spinlock *node, struct mcs_spinlock *prev)
|
||||
static void pv_kick_node(struct qspinlock *lock, struct mcs_spinlock *node)
|
||||
{
|
||||
struct pv_node *pn = (struct pv_node *)node;
|
||||
|
||||
enum vcpu_state old = vcpu_halted;
|
||||
/*
|
||||
* If the vCPU is indeed halted, advance its state to match that of
|
||||
* pv_wait_node(). If OTOH this fails, the vCPU was running and will
|
||||
@@ -377,8 +374,7 @@ static void pv_kick_node(struct qspinlock *lock, struct mcs_spinlock *node)
|
||||
* subsequent writes.
|
||||
*/
|
||||
smp_mb__before_atomic();
|
||||
if (cmpxchg_relaxed(&pn->state, vcpu_halted, vcpu_hashed)
|
||||
!= vcpu_halted)
|
||||
if (!try_cmpxchg_relaxed(&pn->state, &old, vcpu_hashed))
|
||||
return;
|
||||
|
||||
/*
|
||||
@@ -546,15 +542,14 @@ __pv_queued_spin_unlock_slowpath(struct qspinlock *lock, u8 locked)
|
||||
#ifndef __pv_queued_spin_unlock
|
||||
__visible __lockfunc void __pv_queued_spin_unlock(struct qspinlock *lock)
|
||||
{
|
||||
u8 locked;
|
||||
u8 locked = _Q_LOCKED_VAL;
|
||||
|
||||
/*
|
||||
* We must not unlock if SLOW, because in that case we must first
|
||||
* unhash. Otherwise it would be possible to have multiple @lock
|
||||
* entries, which would be BAD.
|
||||
*/
|
||||
locked = cmpxchg_release(&lock->locked, _Q_LOCKED_VAL, 0);
|
||||
if (likely(locked == _Q_LOCKED_VAL))
|
||||
if (try_cmpxchg_release(&lock->locked, &locked, 0))
|
||||
return;
|
||||
|
||||
__pv_queued_spin_unlock_slowpath(lock, locked);
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include <test_progs.h>
|
||||
#include "test_perf_skip.skel.h"
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/hw_breakpoint.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#ifndef TRAP_PERF
|
||||
#define TRAP_PERF 6
|
||||
#endif
|
||||
|
||||
int sigio_count, sigtrap_count;
|
||||
|
||||
static void handle_sigio(int sig __always_unused)
|
||||
{
|
||||
++sigio_count;
|
||||
}
|
||||
|
||||
static void handle_sigtrap(int signum __always_unused,
|
||||
siginfo_t *info,
|
||||
void *ucontext __always_unused)
|
||||
{
|
||||
ASSERT_EQ(info->si_code, TRAP_PERF, "si_code");
|
||||
++sigtrap_count;
|
||||
}
|
||||
|
||||
static noinline int test_function(void)
|
||||
{
|
||||
asm volatile ("");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void serial_test_perf_skip(void)
|
||||
{
|
||||
struct sigaction action = {};
|
||||
struct sigaction previous_sigtrap;
|
||||
sighandler_t previous_sigio = SIG_ERR;
|
||||
struct test_perf_skip *skel = NULL;
|
||||
struct perf_event_attr attr = {};
|
||||
int perf_fd = -1;
|
||||
int err;
|
||||
struct f_owner_ex owner;
|
||||
struct bpf_link *prog_link = NULL;
|
||||
|
||||
action.sa_flags = SA_SIGINFO | SA_NODEFER;
|
||||
action.sa_sigaction = handle_sigtrap;
|
||||
sigemptyset(&action.sa_mask);
|
||||
if (!ASSERT_OK(sigaction(SIGTRAP, &action, &previous_sigtrap), "sigaction"))
|
||||
return;
|
||||
|
||||
previous_sigio = signal(SIGIO, handle_sigio);
|
||||
if (!ASSERT_NEQ(previous_sigio, SIG_ERR, "signal"))
|
||||
goto cleanup;
|
||||
|
||||
skel = test_perf_skip__open_and_load();
|
||||
if (!ASSERT_OK_PTR(skel, "skel_load"))
|
||||
goto cleanup;
|
||||
|
||||
attr.type = PERF_TYPE_BREAKPOINT;
|
||||
attr.size = sizeof(attr);
|
||||
attr.bp_type = HW_BREAKPOINT_X;
|
||||
attr.bp_addr = (uintptr_t)test_function;
|
||||
attr.bp_len = sizeof(long);
|
||||
attr.sample_period = 1;
|
||||
attr.sample_type = PERF_SAMPLE_IP;
|
||||
attr.pinned = 1;
|
||||
attr.exclude_kernel = 1;
|
||||
attr.exclude_hv = 1;
|
||||
attr.precise_ip = 3;
|
||||
attr.sigtrap = 1;
|
||||
attr.remove_on_exec = 1;
|
||||
|
||||
perf_fd = syscall(__NR_perf_event_open, &attr, 0, -1, -1, 0);
|
||||
if (perf_fd < 0 && (errno == ENOENT || errno == EOPNOTSUPP)) {
|
||||
printf("SKIP:no PERF_TYPE_BREAKPOINT/HW_BREAKPOINT_X\n");
|
||||
test__skip();
|
||||
goto cleanup;
|
||||
}
|
||||
if (!ASSERT_OK(perf_fd < 0, "perf_event_open"))
|
||||
goto cleanup;
|
||||
|
||||
/* Configure the perf event to signal on sample. */
|
||||
err = fcntl(perf_fd, F_SETFL, O_ASYNC);
|
||||
if (!ASSERT_OK(err, "fcntl(F_SETFL, O_ASYNC)"))
|
||||
goto cleanup;
|
||||
|
||||
owner.type = F_OWNER_TID;
|
||||
owner.pid = syscall(__NR_gettid);
|
||||
err = fcntl(perf_fd, F_SETOWN_EX, &owner);
|
||||
if (!ASSERT_OK(err, "fcntl(F_SETOWN_EX)"))
|
||||
goto cleanup;
|
||||
|
||||
/* Allow at most one sample. A sample rejected by bpf should
|
||||
* not count against this.
|
||||
*/
|
||||
err = ioctl(perf_fd, PERF_EVENT_IOC_REFRESH, 1);
|
||||
if (!ASSERT_OK(err, "ioctl(PERF_EVENT_IOC_REFRESH)"))
|
||||
goto cleanup;
|
||||
|
||||
prog_link = bpf_program__attach_perf_event(skel->progs.handler, perf_fd);
|
||||
if (!ASSERT_OK_PTR(prog_link, "bpf_program__attach_perf_event"))
|
||||
goto cleanup;
|
||||
|
||||
/* Configure the bpf program to suppress the sample. */
|
||||
skel->bss->ip = (uintptr_t)test_function;
|
||||
test_function();
|
||||
|
||||
ASSERT_EQ(sigio_count, 0, "sigio_count");
|
||||
ASSERT_EQ(sigtrap_count, 0, "sigtrap_count");
|
||||
|
||||
/* Configure the bpf program to allow the sample. */
|
||||
skel->bss->ip = 0;
|
||||
test_function();
|
||||
|
||||
ASSERT_EQ(sigio_count, 1, "sigio_count");
|
||||
ASSERT_EQ(sigtrap_count, 1, "sigtrap_count");
|
||||
|
||||
/* Test that the sample above is the only one allowed (by perf, not
|
||||
* by bpf)
|
||||
*/
|
||||
test_function();
|
||||
|
||||
ASSERT_EQ(sigio_count, 1, "sigio_count");
|
||||
ASSERT_EQ(sigtrap_count, 1, "sigtrap_count");
|
||||
|
||||
cleanup:
|
||||
bpf_link__destroy(prog_link);
|
||||
if (perf_fd >= 0)
|
||||
close(perf_fd);
|
||||
test_perf_skip__destroy(skel);
|
||||
|
||||
if (previous_sigio != SIG_ERR)
|
||||
signal(SIGIO, previous_sigio);
|
||||
sigaction(SIGTRAP, &previous_sigtrap, NULL);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
uintptr_t ip;
|
||||
|
||||
SEC("perf_event")
|
||||
int handler(struct bpf_perf_event_data *data)
|
||||
{
|
||||
/* Skip events that have the correct ip. */
|
||||
return ip != PT_REGS_IP(&data->regs);
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
@@ -1,3 +1,4 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
sigtrap_threads
|
||||
remove_on_exec
|
||||
watermark_signal
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
CFLAGS += -Wl,-no-as-needed -Wall $(KHDR_INCLUDES)
|
||||
LDFLAGS += -lpthread
|
||||
|
||||
TEST_GEN_PROGS := sigtrap_threads remove_on_exec
|
||||
TEST_GEN_PROGS := sigtrap_threads remove_on_exec watermark_signal
|
||||
include ../lib.mk
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/perf_event.h>
|
||||
#include <stddef.h>
|
||||
#include <sched.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../kselftest_harness.h"
|
||||
|
||||
#define __maybe_unused __attribute__((__unused__))
|
||||
|
||||
static int sigio_count;
|
||||
|
||||
static void handle_sigio(int signum __maybe_unused,
|
||||
siginfo_t *oh __maybe_unused,
|
||||
void *uc __maybe_unused)
|
||||
{
|
||||
++sigio_count;
|
||||
}
|
||||
|
||||
static void do_child(void)
|
||||
{
|
||||
raise(SIGSTOP);
|
||||
|
||||
for (int i = 0; i < 20; ++i)
|
||||
sleep(1);
|
||||
|
||||
raise(SIGSTOP);
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
TEST(watermark_signal)
|
||||
{
|
||||
struct perf_event_attr attr;
|
||||
struct perf_event_mmap_page *p = NULL;
|
||||
struct sigaction previous_sigio, sigio = { 0 };
|
||||
pid_t child = -1;
|
||||
int child_status;
|
||||
int fd = -1;
|
||||
long page_size = sysconf(_SC_PAGE_SIZE);
|
||||
|
||||
sigio.sa_sigaction = handle_sigio;
|
||||
EXPECT_EQ(sigaction(SIGIO, &sigio, &previous_sigio), 0);
|
||||
|
||||
memset(&attr, 0, sizeof(attr));
|
||||
attr.size = sizeof(attr);
|
||||
attr.type = PERF_TYPE_SOFTWARE;
|
||||
attr.config = PERF_COUNT_SW_DUMMY;
|
||||
attr.sample_period = 1;
|
||||
attr.disabled = 1;
|
||||
attr.watermark = 1;
|
||||
attr.context_switch = 1;
|
||||
attr.wakeup_watermark = 1;
|
||||
|
||||
child = fork();
|
||||
EXPECT_GE(child, 0);
|
||||
if (child == 0)
|
||||
do_child();
|
||||
else if (child < 0) {
|
||||
perror("fork()");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (waitpid(child, &child_status, WSTOPPED) != child ||
|
||||
!(WIFSTOPPED(child_status) && WSTOPSIG(child_status) == SIGSTOP)) {
|
||||
fprintf(stderr,
|
||||
"failed to sycnhronize with child errno=%d status=%x\n",
|
||||
errno,
|
||||
child_status);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
fd = syscall(__NR_perf_event_open, &attr, child, -1, -1,
|
||||
PERF_FLAG_FD_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
fprintf(stderr, "failed opening event %llx\n", attr.config);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (fcntl(fd, F_SETFL, FASYNC)) {
|
||||
perror("F_SETFL FASYNC");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (fcntl(fd, F_SETOWN, getpid())) {
|
||||
perror("F_SETOWN getpid()");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (fcntl(fd, F_SETSIG, SIGIO)) {
|
||||
perror("F_SETSIG SIGIO");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
p = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (p == NULL) {
|
||||
perror("mmap");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (ioctl(fd, PERF_EVENT_IOC_ENABLE, 0)) {
|
||||
perror("PERF_EVENT_IOC_ENABLE");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (kill(child, SIGCONT) < 0) {
|
||||
perror("SIGCONT");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (waitpid(child, &child_status, WSTOPPED) != -1 || errno != EINTR)
|
||||
fprintf(stderr,
|
||||
"expected SIGIO to terminate wait errno=%d status=%x\n%d",
|
||||
errno,
|
||||
child_status,
|
||||
sigio_count);
|
||||
|
||||
EXPECT_GE(sigio_count, 1);
|
||||
|
||||
cleanup:
|
||||
if (p != NULL)
|
||||
munmap(p, 2 * page_size);
|
||||
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
|
||||
if (child > 0) {
|
||||
kill(child, SIGKILL);
|
||||
waitpid(child, NULL, 0);
|
||||
}
|
||||
|
||||
sigaction(SIGIO, &previous_sigio, NULL);
|
||||
}
|
||||
|
||||
TEST_HARNESS_MAIN
|
||||
Reference in New Issue
Block a user