!249 Backport pseudo NMI for PMU
Merge pull request !249 from lcy/devel-35
This commit is contained in:
commit
9a02555d16
|
@ -372,6 +372,73 @@ static inline bool armv8pmu_event_is_chained(struct perf_event *event)
|
||||||
#define ARMV8_IDX_TO_COUNTER(x) \
|
#define ARMV8_IDX_TO_COUNTER(x) \
|
||||||
(((x) - ARMV8_IDX_COUNTER0) & ARMV8_PMU_COUNTER_MASK)
|
(((x) - ARMV8_IDX_COUNTER0) & ARMV8_PMU_COUNTER_MASK)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This code is really good
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define PMEVN_CASE(n, case_macro) \
|
||||||
|
case n: case_macro(n); break
|
||||||
|
|
||||||
|
#define PMEVN_SWITCH(x, case_macro) \
|
||||||
|
do { \
|
||||||
|
switch (x) { \
|
||||||
|
PMEVN_CASE(0, case_macro); \
|
||||||
|
PMEVN_CASE(1, case_macro); \
|
||||||
|
PMEVN_CASE(2, case_macro); \
|
||||||
|
PMEVN_CASE(3, case_macro); \
|
||||||
|
PMEVN_CASE(4, case_macro); \
|
||||||
|
PMEVN_CASE(5, case_macro); \
|
||||||
|
PMEVN_CASE(6, case_macro); \
|
||||||
|
PMEVN_CASE(7, case_macro); \
|
||||||
|
PMEVN_CASE(8, case_macro); \
|
||||||
|
PMEVN_CASE(9, case_macro); \
|
||||||
|
PMEVN_CASE(10, case_macro); \
|
||||||
|
PMEVN_CASE(11, case_macro); \
|
||||||
|
PMEVN_CASE(12, case_macro); \
|
||||||
|
PMEVN_CASE(13, case_macro); \
|
||||||
|
PMEVN_CASE(14, case_macro); \
|
||||||
|
PMEVN_CASE(15, case_macro); \
|
||||||
|
PMEVN_CASE(16, case_macro); \
|
||||||
|
PMEVN_CASE(17, case_macro); \
|
||||||
|
PMEVN_CASE(18, case_macro); \
|
||||||
|
PMEVN_CASE(19, case_macro); \
|
||||||
|
PMEVN_CASE(20, case_macro); \
|
||||||
|
PMEVN_CASE(21, case_macro); \
|
||||||
|
PMEVN_CASE(22, case_macro); \
|
||||||
|
PMEVN_CASE(23, case_macro); \
|
||||||
|
PMEVN_CASE(24, case_macro); \
|
||||||
|
PMEVN_CASE(25, case_macro); \
|
||||||
|
PMEVN_CASE(26, case_macro); \
|
||||||
|
PMEVN_CASE(27, case_macro); \
|
||||||
|
PMEVN_CASE(28, case_macro); \
|
||||||
|
PMEVN_CASE(29, case_macro); \
|
||||||
|
PMEVN_CASE(30, case_macro); \
|
||||||
|
default: WARN(1, "Invalid PMEV* index\n"); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define RETURN_READ_PMEVCNTRN(n) \
|
||||||
|
return read_sysreg(pmevcntr##n##_el0)
|
||||||
|
static unsigned long read_pmevcntrn(int n)
|
||||||
|
{
|
||||||
|
PMEVN_SWITCH(n, RETURN_READ_PMEVCNTRN);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define WRITE_PMEVCNTRN(n) \
|
||||||
|
write_sysreg(val, pmevcntr##n##_el0)
|
||||||
|
static void write_pmevcntrn(int n, unsigned long val)
|
||||||
|
{
|
||||||
|
PMEVN_SWITCH(n, WRITE_PMEVCNTRN);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define WRITE_PMEVTYPERN(n) \
|
||||||
|
write_sysreg(val, pmevtyper##n##_el0)
|
||||||
|
static void write_pmevtypern(int n, unsigned long val)
|
||||||
|
{
|
||||||
|
PMEVN_SWITCH(n, WRITE_PMEVTYPERN);
|
||||||
|
}
|
||||||
|
|
||||||
static inline u32 armv8pmu_pmcr_read(void)
|
static inline u32 armv8pmu_pmcr_read(void)
|
||||||
{
|
{
|
||||||
return read_sysreg(pmcr_el0);
|
return read_sysreg(pmcr_el0);
|
||||||
|
@ -400,17 +467,11 @@ static inline int armv8pmu_counter_has_overflowed(u32 pmnc, int idx)
|
||||||
return pmnc & BIT(ARMV8_IDX_TO_COUNTER(idx));
|
return pmnc & BIT(ARMV8_IDX_TO_COUNTER(idx));
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void armv8pmu_select_counter(int idx)
|
|
||||||
{
|
|
||||||
u32 counter = ARMV8_IDX_TO_COUNTER(idx);
|
|
||||||
write_sysreg(counter, pmselr_el0);
|
|
||||||
isb();
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline u32 armv8pmu_read_evcntr(int idx)
|
static inline u32 armv8pmu_read_evcntr(int idx)
|
||||||
{
|
{
|
||||||
armv8pmu_select_counter(idx);
|
u32 counter = ARMV8_IDX_TO_COUNTER(idx);
|
||||||
return read_sysreg(pmxevcntr_el0);
|
|
||||||
|
return read_pmevcntrn(counter);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline u64 armv8pmu_read_hw_counter(struct perf_event *event)
|
static inline u64 armv8pmu_read_hw_counter(struct perf_event *event)
|
||||||
|
@ -444,8 +505,9 @@ static u64 armv8pmu_read_counter(struct perf_event *event)
|
||||||
|
|
||||||
static inline void armv8pmu_write_evcntr(int idx, u32 value)
|
static inline void armv8pmu_write_evcntr(int idx, u32 value)
|
||||||
{
|
{
|
||||||
armv8pmu_select_counter(idx);
|
u32 counter = ARMV8_IDX_TO_COUNTER(idx);
|
||||||
write_sysreg(value, pmxevcntr_el0);
|
|
||||||
|
write_pmevcntrn(counter, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void armv8pmu_write_hw_counter(struct perf_event *event,
|
static inline void armv8pmu_write_hw_counter(struct perf_event *event,
|
||||||
|
@ -486,9 +548,10 @@ static void armv8pmu_write_counter(struct perf_event *event, u64 value)
|
||||||
|
|
||||||
static inline void armv8pmu_write_evtype(int idx, u32 val)
|
static inline void armv8pmu_write_evtype(int idx, u32 val)
|
||||||
{
|
{
|
||||||
armv8pmu_select_counter(idx);
|
u32 counter = ARMV8_IDX_TO_COUNTER(idx);
|
||||||
|
|
||||||
val &= ARMV8_PMU_EVTYPE_MASK;
|
val &= ARMV8_PMU_EVTYPE_MASK;
|
||||||
write_sysreg(val, pmxevtyper_el0);
|
write_pmevtypern(counter, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void armv8pmu_write_event_type(struct perf_event *event)
|
static inline void armv8pmu_write_event_type(struct perf_event *event)
|
||||||
|
@ -508,6 +571,9 @@ static inline void armv8pmu_write_event_type(struct perf_event *event)
|
||||||
armv8pmu_write_evtype(idx - 1, hwc->config_base);
|
armv8pmu_write_evtype(idx - 1, hwc->config_base);
|
||||||
armv8pmu_write_evtype(idx, chain_evt);
|
armv8pmu_write_evtype(idx, chain_evt);
|
||||||
} else {
|
} else {
|
||||||
|
if (idx == ARMV8_IDX_CYCLE_COUNTER)
|
||||||
|
write_sysreg(hwc->config_base, pmccfiltr_el0);
|
||||||
|
else
|
||||||
armv8pmu_write_evtype(idx, hwc->config_base);
|
armv8pmu_write_evtype(idx, hwc->config_base);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -515,6 +581,11 @@ static inline void armv8pmu_write_event_type(struct perf_event *event)
|
||||||
static inline int armv8pmu_enable_counter(int idx)
|
static inline int armv8pmu_enable_counter(int idx)
|
||||||
{
|
{
|
||||||
u32 counter = ARMV8_IDX_TO_COUNTER(idx);
|
u32 counter = ARMV8_IDX_TO_COUNTER(idx);
|
||||||
|
/*
|
||||||
|
* Make sure event configuration register writes are visible before we
|
||||||
|
* enable the counter.
|
||||||
|
* */
|
||||||
|
isb();
|
||||||
write_sysreg(BIT(counter), pmcntenset_el0);
|
write_sysreg(BIT(counter), pmcntenset_el0);
|
||||||
return idx;
|
return idx;
|
||||||
}
|
}
|
||||||
|
@ -542,6 +613,11 @@ static inline int armv8pmu_disable_counter(int idx)
|
||||||
{
|
{
|
||||||
u32 counter = ARMV8_IDX_TO_COUNTER(idx);
|
u32 counter = ARMV8_IDX_TO_COUNTER(idx);
|
||||||
write_sysreg(BIT(counter), pmcntenclr_el0);
|
write_sysreg(BIT(counter), pmcntenclr_el0);
|
||||||
|
/*
|
||||||
|
* Make sure the effects of disabling the counter are visible before we
|
||||||
|
* start configuring the event.
|
||||||
|
*/
|
||||||
|
isb();
|
||||||
return idx;
|
return idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -610,15 +686,10 @@ static inline u32 armv8pmu_getreset_flags(void)
|
||||||
|
|
||||||
static void armv8pmu_enable_event(struct perf_event *event)
|
static void armv8pmu_enable_event(struct perf_event *event)
|
||||||
{
|
{
|
||||||
unsigned long flags;
|
|
||||||
struct arm_pmu *cpu_pmu = to_arm_pmu(event->pmu);
|
|
||||||
struct pmu_hw_events *events = this_cpu_ptr(cpu_pmu->hw_events);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Enable counter and interrupt, and set the counter to count
|
* Enable counter and interrupt, and set the counter to count
|
||||||
* the event that we're interested in.
|
* the event that we're interested in.
|
||||||
*/
|
*/
|
||||||
raw_spin_lock_irqsave(&events->pmu_lock, flags);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Disable counter
|
* Disable counter
|
||||||
|
@ -626,7 +697,7 @@ static void armv8pmu_enable_event(struct perf_event *event)
|
||||||
armv8pmu_disable_event_counter(event);
|
armv8pmu_disable_event_counter(event);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set event (if destined for PMNx counters).
|
* Set event.
|
||||||
*/
|
*/
|
||||||
armv8pmu_write_event_type(event);
|
armv8pmu_write_event_type(event);
|
||||||
|
|
||||||
|
@ -639,21 +710,10 @@ static void armv8pmu_enable_event(struct perf_event *event)
|
||||||
* Enable counter
|
* Enable counter
|
||||||
*/
|
*/
|
||||||
armv8pmu_enable_event_counter(event);
|
armv8pmu_enable_event_counter(event);
|
||||||
|
|
||||||
raw_spin_unlock_irqrestore(&events->pmu_lock, flags);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void armv8pmu_disable_event(struct perf_event *event)
|
static void armv8pmu_disable_event(struct perf_event *event)
|
||||||
{
|
{
|
||||||
unsigned long flags;
|
|
||||||
struct arm_pmu *cpu_pmu = to_arm_pmu(event->pmu);
|
|
||||||
struct pmu_hw_events *events = this_cpu_ptr(cpu_pmu->hw_events);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Disable counter and interrupt
|
|
||||||
*/
|
|
||||||
raw_spin_lock_irqsave(&events->pmu_lock, flags);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Disable counter
|
* Disable counter
|
||||||
*/
|
*/
|
||||||
|
@ -663,30 +723,18 @@ static void armv8pmu_disable_event(struct perf_event *event)
|
||||||
* Disable interrupt for this counter
|
* Disable interrupt for this counter
|
||||||
*/
|
*/
|
||||||
armv8pmu_disable_event_irq(event);
|
armv8pmu_disable_event_irq(event);
|
||||||
|
|
||||||
raw_spin_unlock_irqrestore(&events->pmu_lock, flags);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void armv8pmu_start(struct arm_pmu *cpu_pmu)
|
static void armv8pmu_start(struct arm_pmu *cpu_pmu)
|
||||||
{
|
{
|
||||||
unsigned long flags;
|
|
||||||
struct pmu_hw_events *events = this_cpu_ptr(cpu_pmu->hw_events);
|
|
||||||
|
|
||||||
raw_spin_lock_irqsave(&events->pmu_lock, flags);
|
|
||||||
/* Enable all counters */
|
/* Enable all counters */
|
||||||
armv8pmu_pmcr_write(armv8pmu_pmcr_read() | ARMV8_PMU_PMCR_E);
|
armv8pmu_pmcr_write(armv8pmu_pmcr_read() | ARMV8_PMU_PMCR_E);
|
||||||
raw_spin_unlock_irqrestore(&events->pmu_lock, flags);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void armv8pmu_stop(struct arm_pmu *cpu_pmu)
|
static void armv8pmu_stop(struct arm_pmu *cpu_pmu)
|
||||||
{
|
{
|
||||||
unsigned long flags;
|
|
||||||
struct pmu_hw_events *events = this_cpu_ptr(cpu_pmu->hw_events);
|
|
||||||
|
|
||||||
raw_spin_lock_irqsave(&events->pmu_lock, flags);
|
|
||||||
/* Disable all counters */
|
/* Disable all counters */
|
||||||
armv8pmu_pmcr_write(armv8pmu_pmcr_read() & ~ARMV8_PMU_PMCR_E);
|
armv8pmu_pmcr_write(armv8pmu_pmcr_read() & ~ARMV8_PMU_PMCR_E);
|
||||||
raw_spin_unlock_irqrestore(&events->pmu_lock, flags);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static irqreturn_t armv8pmu_handle_irq(struct arm_pmu *cpu_pmu)
|
static irqreturn_t armv8pmu_handle_irq(struct arm_pmu *cpu_pmu)
|
||||||
|
@ -739,20 +787,16 @@ static irqreturn_t armv8pmu_handle_irq(struct arm_pmu *cpu_pmu)
|
||||||
if (!armpmu_event_set_period(event))
|
if (!armpmu_event_set_period(event))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Perf event overflow will queue the processing of the event as
|
||||||
|
* an irq_work which will be taken care of in the handling of
|
||||||
|
* IPI_IRQ_WORK.
|
||||||
|
*/
|
||||||
if (perf_event_overflow(event, &data, regs))
|
if (perf_event_overflow(event, &data, regs))
|
||||||
cpu_pmu->disable(event);
|
cpu_pmu->disable(event);
|
||||||
}
|
}
|
||||||
armv8pmu_start(cpu_pmu);
|
armv8pmu_start(cpu_pmu);
|
||||||
|
|
||||||
/*
|
|
||||||
* Handle the pending perf events.
|
|
||||||
*
|
|
||||||
* Note: this call *must* be run with interrupts disabled. For
|
|
||||||
* platforms that can have the PMU interrupts raised as an NMI, this
|
|
||||||
* will not work.
|
|
||||||
*/
|
|
||||||
irq_work_run();
|
|
||||||
|
|
||||||
return IRQ_HANDLED;
|
return IRQ_HANDLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,8 +26,84 @@
|
||||||
|
|
||||||
#include <asm/irq_regs.h>
|
#include <asm/irq_regs.h>
|
||||||
|
|
||||||
|
static int armpmu_count_irq_users(const int irq);
|
||||||
|
|
||||||
|
struct pmu_irq_ops {
|
||||||
|
void (*enable_pmuirq)(unsigned int irq);
|
||||||
|
void (*disable_pmuirq)(unsigned int irq);
|
||||||
|
void (*free_pmuirq)(unsigned int irq, int cpu, void __percpu *devid);
|
||||||
|
};
|
||||||
|
|
||||||
|
static void armpmu_free_pmuirq(unsigned int irq, int cpu, void __percpu *devid)
|
||||||
|
{
|
||||||
|
free_irq(irq, per_cpu_ptr(devid, cpu));
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct pmu_irq_ops pmuirq_ops = {
|
||||||
|
.enable_pmuirq = enable_irq,
|
||||||
|
.disable_pmuirq = disable_irq_nosync,
|
||||||
|
.free_pmuirq = armpmu_free_pmuirq
|
||||||
|
};
|
||||||
|
|
||||||
|
static void armpmu_free_pmunmi(unsigned int irq, int cpu, void __percpu *devid)
|
||||||
|
{
|
||||||
|
free_nmi(irq, per_cpu_ptr(devid, cpu));
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct pmu_irq_ops pmunmi_ops = {
|
||||||
|
.enable_pmuirq = enable_nmi,
|
||||||
|
.disable_pmuirq = disable_nmi_nosync,
|
||||||
|
.free_pmuirq = armpmu_free_pmunmi
|
||||||
|
};
|
||||||
|
|
||||||
|
static void armpmu_enable_percpu_pmuirq(unsigned int irq)
|
||||||
|
{
|
||||||
|
enable_percpu_irq(irq, IRQ_TYPE_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void armpmu_free_percpu_pmuirq(unsigned int irq, int cpu,
|
||||||
|
void __percpu *devid)
|
||||||
|
{
|
||||||
|
if (armpmu_count_irq_users(irq) == 1)
|
||||||
|
free_percpu_irq(irq, devid);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct pmu_irq_ops percpu_pmuirq_ops = {
|
||||||
|
.enable_pmuirq = armpmu_enable_percpu_pmuirq,
|
||||||
|
.disable_pmuirq = disable_percpu_irq,
|
||||||
|
.free_pmuirq = armpmu_free_percpu_pmuirq
|
||||||
|
};
|
||||||
|
|
||||||
|
static void armpmu_enable_percpu_pmunmi(unsigned int irq)
|
||||||
|
{
|
||||||
|
if (!prepare_percpu_nmi(irq))
|
||||||
|
enable_percpu_nmi(irq, IRQ_TYPE_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void armpmu_disable_percpu_pmunmi(unsigned int irq)
|
||||||
|
{
|
||||||
|
disable_percpu_nmi(irq);
|
||||||
|
teardown_percpu_nmi(irq);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void armpmu_free_percpu_pmunmi(unsigned int irq, int cpu,
|
||||||
|
void __percpu *devid)
|
||||||
|
{
|
||||||
|
if (armpmu_count_irq_users(irq) == 1)
|
||||||
|
free_percpu_nmi(irq, devid);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct pmu_irq_ops percpu_pmunmi_ops = {
|
||||||
|
.enable_pmuirq = armpmu_enable_percpu_pmunmi,
|
||||||
|
.disable_pmuirq = armpmu_disable_percpu_pmunmi,
|
||||||
|
.free_pmuirq = armpmu_free_percpu_pmunmi
|
||||||
|
};
|
||||||
|
|
||||||
static DEFINE_PER_CPU(struct arm_pmu *, cpu_armpmu);
|
static DEFINE_PER_CPU(struct arm_pmu *, cpu_armpmu);
|
||||||
static DEFINE_PER_CPU(int, cpu_irq);
|
static DEFINE_PER_CPU(int, cpu_irq);
|
||||||
|
static DEFINE_PER_CPU(const struct pmu_irq_ops *, cpu_irq_ops);
|
||||||
|
|
||||||
|
static bool has_nmi;
|
||||||
|
|
||||||
static inline u64 arm_pmu_event_max_period(struct perf_event *event)
|
static inline u64 arm_pmu_event_max_period(struct perf_event *event)
|
||||||
{
|
{
|
||||||
|
@ -542,6 +618,23 @@ static int armpmu_count_irq_users(const int irq)
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const struct pmu_irq_ops *armpmu_find_irq_ops(int irq)
|
||||||
|
{
|
||||||
|
const struct pmu_irq_ops *ops = NULL;
|
||||||
|
int cpu;
|
||||||
|
|
||||||
|
for_each_possible_cpu(cpu) {
|
||||||
|
if (per_cpu(cpu_irq, cpu) != irq)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ops = per_cpu(cpu_irq_ops, cpu);
|
||||||
|
if (ops)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ops;
|
||||||
|
}
|
||||||
|
|
||||||
void armpmu_free_irq(int irq, int cpu)
|
void armpmu_free_irq(int irq, int cpu)
|
||||||
{
|
{
|
||||||
if (per_cpu(cpu_irq, cpu) == 0)
|
if (per_cpu(cpu_irq, cpu) == 0)
|
||||||
|
@ -549,18 +642,18 @@ void armpmu_free_irq(int irq, int cpu)
|
||||||
if (WARN_ON(irq != per_cpu(cpu_irq, cpu)))
|
if (WARN_ON(irq != per_cpu(cpu_irq, cpu)))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!irq_is_percpu_devid(irq))
|
per_cpu(cpu_irq_ops, cpu)->free_pmuirq(irq, cpu, &cpu_armpmu);
|
||||||
free_irq(irq, per_cpu_ptr(&cpu_armpmu, cpu));
|
|
||||||
else if (armpmu_count_irq_users(irq) == 1)
|
|
||||||
free_percpu_irq(irq, &cpu_armpmu);
|
|
||||||
|
|
||||||
per_cpu(cpu_irq, cpu) = 0;
|
per_cpu(cpu_irq, cpu) = 0;
|
||||||
|
per_cpu(cpu_irq_ops, cpu) = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
int armpmu_request_irq(int irq, int cpu)
|
int armpmu_request_irq(int irq, int cpu)
|
||||||
{
|
{
|
||||||
int err = 0;
|
int err = 0;
|
||||||
const irq_handler_t handler = armpmu_dispatch_irq;
|
const irq_handler_t handler = armpmu_dispatch_irq;
|
||||||
|
const struct pmu_irq_ops *irq_ops;
|
||||||
|
|
||||||
if (!irq)
|
if (!irq)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
@ -580,17 +673,44 @@ int armpmu_request_irq(int irq, int cpu)
|
||||||
IRQF_NO_THREAD;
|
IRQF_NO_THREAD;
|
||||||
|
|
||||||
irq_set_status_flags(irq, IRQ_NOAUTOEN);
|
irq_set_status_flags(irq, IRQ_NOAUTOEN);
|
||||||
|
|
||||||
|
err = request_nmi(irq, handler, irq_flags, "arm-pmu",
|
||||||
|
per_cpu_ptr(&cpu_armpmu, cpu));
|
||||||
|
|
||||||
|
/* If cannot get an NMI, get a normal interrupt */
|
||||||
|
if (err) {
|
||||||
err = request_irq(irq, handler, irq_flags, "arm-pmu",
|
err = request_irq(irq, handler, irq_flags, "arm-pmu",
|
||||||
per_cpu_ptr(&cpu_armpmu, cpu));
|
per_cpu_ptr(&cpu_armpmu, cpu));
|
||||||
|
irq_ops = &pmuirq_ops;
|
||||||
|
} else {
|
||||||
|
has_nmi = true;
|
||||||
|
irq_ops = &pmunmi_ops;
|
||||||
|
}
|
||||||
} else if (armpmu_count_irq_users(irq) == 0) {
|
} else if (armpmu_count_irq_users(irq) == 0) {
|
||||||
|
err = request_percpu_nmi(irq, handler, "arm-pmu", &cpu_armpmu);
|
||||||
|
|
||||||
|
/* If cannot get an NMI, get a normal interrupt */
|
||||||
|
if (err) {
|
||||||
err = request_percpu_irq(irq, handler, "arm-pmu",
|
err = request_percpu_irq(irq, handler, "arm-pmu",
|
||||||
&cpu_armpmu);
|
&cpu_armpmu);
|
||||||
|
irq_ops = &percpu_pmuirq_ops;
|
||||||
|
} else {
|
||||||
|
has_nmi= true;
|
||||||
|
irq_ops = &percpu_pmunmi_ops;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Per cpudevid irq was already requested by another CPU */
|
||||||
|
irq_ops = armpmu_find_irq_ops(irq);
|
||||||
|
|
||||||
|
if (WARN_ON(!irq_ops))
|
||||||
|
err = -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err)
|
if (err)
|
||||||
goto err_out;
|
goto err_out;
|
||||||
|
|
||||||
per_cpu(cpu_irq, cpu) = irq;
|
per_cpu(cpu_irq, cpu) = irq;
|
||||||
|
per_cpu(cpu_irq_ops, cpu) = irq_ops;
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
err_out:
|
err_out:
|
||||||
|
@ -623,12 +743,8 @@ static int arm_perf_starting_cpu(unsigned int cpu, struct hlist_node *node)
|
||||||
per_cpu(cpu_armpmu, cpu) = pmu;
|
per_cpu(cpu_armpmu, cpu) = pmu;
|
||||||
|
|
||||||
irq = armpmu_get_cpu_irq(pmu, cpu);
|
irq = armpmu_get_cpu_irq(pmu, cpu);
|
||||||
if (irq) {
|
if (irq)
|
||||||
if (irq_is_percpu_devid(irq))
|
per_cpu(cpu_irq_ops, cpu)->enable_pmuirq(irq);
|
||||||
enable_percpu_irq(irq, IRQ_TYPE_NONE);
|
|
||||||
else
|
|
||||||
enable_irq(irq);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -642,12 +758,8 @@ static int arm_perf_teardown_cpu(unsigned int cpu, struct hlist_node *node)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
irq = armpmu_get_cpu_irq(pmu, cpu);
|
irq = armpmu_get_cpu_irq(pmu, cpu);
|
||||||
if (irq) {
|
if (irq)
|
||||||
if (irq_is_percpu_devid(irq))
|
per_cpu(cpu_irq_ops, cpu)->disable_pmuirq(irq);
|
||||||
disable_percpu_irq(irq);
|
|
||||||
else
|
|
||||||
disable_irq_nosync(irq);
|
|
||||||
}
|
|
||||||
|
|
||||||
per_cpu(cpu_armpmu, cpu) = NULL;
|
per_cpu(cpu_armpmu, cpu) = NULL;
|
||||||
|
|
||||||
|
@ -868,8 +980,9 @@ int armpmu_register(struct arm_pmu *pmu)
|
||||||
if (!__oprofile_cpu_pmu)
|
if (!__oprofile_cpu_pmu)
|
||||||
__oprofile_cpu_pmu = pmu;
|
__oprofile_cpu_pmu = pmu;
|
||||||
|
|
||||||
pr_info("enabled with %s PMU driver, %d counters available\n",
|
pr_info("enabled with %s PMU driver, %d counters available%s\n",
|
||||||
pmu->name, pmu->num_events);
|
pmu->name, pmu->num_events,
|
||||||
|
has_nmi ? ", using NMIs" : "");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ struct kvm_pmu {
|
||||||
bool ready;
|
bool ready;
|
||||||
bool created;
|
bool created;
|
||||||
bool irq_level;
|
bool irq_level;
|
||||||
|
struct irq_work overflow_work;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define kvm_arm_pmu_v3_ready(v) ((v)->arch.pmu.ready)
|
#define kvm_arm_pmu_v3_ready(v) ((v)->arch.pmu.ready)
|
||||||
|
|
|
@ -259,6 +259,7 @@ void kvm_pmu_vcpu_destroy(struct kvm_vcpu *vcpu)
|
||||||
|
|
||||||
for (i = 0; i < ARMV8_PMU_MAX_COUNTERS; i++)
|
for (i = 0; i < ARMV8_PMU_MAX_COUNTERS; i++)
|
||||||
kvm_pmu_release_perf_event(&pmu->pmc[i]);
|
kvm_pmu_release_perf_event(&pmu->pmc[i]);
|
||||||
|
irq_work_sync(&vcpu->arch.pmu.overflow_work);
|
||||||
}
|
}
|
||||||
|
|
||||||
u64 kvm_pmu_valid_counter_mask(struct kvm_vcpu *vcpu)
|
u64 kvm_pmu_valid_counter_mask(struct kvm_vcpu *vcpu)
|
||||||
|
@ -435,6 +436,22 @@ void kvm_pmu_sync_hwstate(struct kvm_vcpu *vcpu)
|
||||||
kvm_pmu_update_state(vcpu);
|
kvm_pmu_update_state(vcpu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When perf interrupt is an NMI, we cannot safely notify the vcpu corresponding
|
||||||
|
* to the event.
|
||||||
|
* This is why we need a callback to do it once outside of the NMI context.
|
||||||
|
*/
|
||||||
|
static void kvm_pmu_perf_overflow_notify_vcpu(struct irq_work *work)
|
||||||
|
{
|
||||||
|
struct kvm_vcpu *vcpu;
|
||||||
|
struct kvm_pmu *pmu;
|
||||||
|
|
||||||
|
pmu = container_of(work, struct kvm_pmu, overflow_work);
|
||||||
|
vcpu = kvm_pmc_to_vcpu(pmu->pmc);
|
||||||
|
|
||||||
|
kvm_vcpu_kick(vcpu);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When the perf event overflows, set the overflow status and inform the vcpu.
|
* When the perf event overflows, set the overflow status and inform the vcpu.
|
||||||
*/
|
*/
|
||||||
|
@ -467,7 +484,11 @@ static void kvm_pmu_perf_overflow(struct perf_event *perf_event,
|
||||||
|
|
||||||
if (kvm_pmu_overflow_status(vcpu)) {
|
if (kvm_pmu_overflow_status(vcpu)) {
|
||||||
kvm_make_request(KVM_REQ_IRQ_PENDING, vcpu);
|
kvm_make_request(KVM_REQ_IRQ_PENDING, vcpu);
|
||||||
|
|
||||||
|
if (!in_nmi())
|
||||||
kvm_vcpu_kick(vcpu);
|
kvm_vcpu_kick(vcpu);
|
||||||
|
else
|
||||||
|
irq_work_queue(&vcpu->arch.pmu.overflow_work);
|
||||||
}
|
}
|
||||||
|
|
||||||
cpu_pmu->pmu.start(perf_event, PERF_EF_RELOAD);
|
cpu_pmu->pmu.start(perf_event, PERF_EF_RELOAD);
|
||||||
|
@ -760,6 +781,9 @@ static int kvm_arm_pmu_v3_init(struct kvm_vcpu *vcpu)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init_irq_work(&vcpu->arch.pmu.overflow_work,
|
||||||
|
kvm_pmu_perf_overflow_notify_vcpu);
|
||||||
|
|
||||||
vcpu->arch.pmu.created = true;
|
vcpu->arch.pmu.created = true;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue