summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/arm64/kernel/perf_debug.c1
-rw-r--r--arch/arm64/kernel/perf_event.c33
-rw-r--r--drivers/perf/arm_pmu.c176
-rw-r--r--include/linux/perf/arm_pmu.h5
4 files changed, 206 insertions, 9 deletions
diff --git a/arch/arm64/kernel/perf_debug.c b/arch/arm64/kernel/perf_debug.c
index ef3313fd16c6..08e93a71fa7d 100644
--- a/arch/arm64/kernel/perf_debug.c
+++ b/arch/arm64/kernel/perf_debug.c
@@ -24,6 +24,7 @@
static char *descriptions =
" 0 arm64: perf: add debug patch logging framework\n"
" 1 Perf: arm64: Add L1 counters to tracepoints\n"
+ " 4 Perf: arm64: support hotplug and power collapse\n"
" 5 Perf: arm64: add perf user-mode permissions\n"
" 6 Perf: arm64: Add debugfs node to clear PMU\n"
" 7 Perf: arm64: Update PMU force reset\n"
diff --git a/arch/arm64/kernel/perf_event.c b/arch/arm64/kernel/perf_event.c
index a03c001a19e7..ce8eb8f565e1 100644
--- a/arch/arm64/kernel/perf_event.c
+++ b/arch/arm64/kernel/perf_event.c
@@ -25,6 +25,8 @@
#include <linux/perf/arm_pmu.h>
#include <linux/platform_device.h>
+static DEFINE_PER_CPU(u32, armv8_pm_pmuserenr);
+
/*
* ARMv8 PMUv3 Performance Events handling code.
* Common event types.
@@ -366,6 +368,7 @@ static void armv8pmu_enable_event(struct perf_event *event)
struct arm_pmu *cpu_pmu = to_arm_pmu(event->pmu);
struct pmu_hw_events *events = this_cpu_ptr(cpu_pmu->hw_events);
int idx = hwc->idx;
+ u64 prev_count = local64_read(&hwc->prev_count);
/*
* Enable counter and interrupt, and set the counter to count
@@ -389,6 +392,11 @@ static void armv8pmu_enable_event(struct perf_event *event)
armv8pmu_enable_intens(idx);
/*
+ * Restore previous value
+ */
+ armv8pmu_write_counter(event, prev_count & 0xffffffff);
+
+ /*
* Enable counter
*/
armv8pmu_enable_counter(idx);
@@ -599,6 +607,27 @@ static int armv8_pmuv3_map_event(struct perf_event *event)
ARMV8_EVTYPE_EVENT);
}
+static void armv8pmu_save_pm_registers(void *hcpu)
+{
+ u32 val;
+ u64 lcpu = (u64)hcpu;
+ int cpu = (int)lcpu;
+
+ asm volatile("mrs %0, pmuserenr_el0" : "=r" (val));
+ per_cpu(armv8_pm_pmuserenr, cpu) = val;
+}
+
+static void armv8pmu_restore_pm_registers(void *hcpu)
+{
+ u32 val;
+ u64 lcpu = (u64)hcpu;
+ int cpu = (int)lcpu;
+
+ val = per_cpu(armv8_pm_pmuserenr, cpu);
+ if (val != 0)
+ asm volatile("msr pmuserenr_el0, %0" :: "r" (val));
+}
+
static int armv8_a53_map_event(struct perf_event *event)
{
return armpmu_map_event(event, &armv8_a53_perf_map,
@@ -642,6 +671,8 @@ void armv8_pmu_init(struct arm_pmu *cpu_pmu)
cpu_pmu->start = armv8pmu_start,
cpu_pmu->stop = armv8pmu_stop,
cpu_pmu->reset = armv8pmu_reset,
+ cpu_pmu->save_pm_registers = armv8pmu_save_pm_registers,
+ cpu_pmu->restore_pm_registers = armv8pmu_restore_pm_registers,
cpu_pmu->max_period = (1LLU << 32) - 1,
cpu_pmu->set_event_filter = armv8pmu_set_event_filter;
}
@@ -674,7 +705,7 @@ static const struct of_device_id armv8_pmu_of_device_ids[] = {
{.compatible = "arm,armv8-pmuv3", .data = armv8_pmuv3_init},
{.compatible = "arm,cortex-a53-pmu", .data = armv8_a53_pmu_init},
{.compatible = "arm,cortex-a57-pmu", .data = armv8_a57_pmu_init},
- {.compatible = "qcom,kryo-pmuv3", .data = kryo_pmu_init},
+ {.compatible = "qcom,kryo-pmuv3", .data = kryo_pmu_init},
{},
};
diff --git a/drivers/perf/arm_pmu.c b/drivers/perf/arm_pmu.c
index a6293cbad0ba..bb6f8bdbf01b 100644
--- a/drivers/perf/arm_pmu.c
+++ b/drivers/perf/arm_pmu.c
@@ -23,10 +23,14 @@
#include <linux/irq.h>
#include <linux/irqdesc.h>
#include <linux/debugfs.h>
+#include <linux/cpu_pm.h>
+#include <linux/percpu.h>
#include <asm/cputype.h>
#include <asm/irq_regs.h>
+static DEFINE_PER_CPU(u32, from_idle);
+
static int
armpmu_map_cache_event(const unsigned (*cache_map)
[PERF_COUNT_HW_CACHE_MAX]
@@ -502,11 +506,28 @@ static void armpmu_enable(struct pmu *pmu)
struct arm_pmu *armpmu = to_arm_pmu(pmu);
struct pmu_hw_events *hw_events = this_cpu_ptr(armpmu->hw_events);
int enabled = bitmap_weight(hw_events->used_mask, armpmu->num_events);
+ int idx;
/* For task-bound events we may be called on other CPUs */
if (!cpumask_test_cpu(smp_processor_id(), &armpmu->supported_cpus))
return;
+ if (hw_events->from_idle) {
+ for (idx = 0; idx <= armpmu->num_events; ++idx) {
+ struct perf_event *event = hw_events->events[idx];
+
+ if (!event)
+ continue;
+
+ armpmu->enable(event);
+ }
+
+ /* Reset bit so we don't needlessly re-enable counters.*/
+ *hw_events->from_idle = 0;
+ /* Don't start the PMU before enabling counters after idle. */
+ isb();
+ }
+
if (enabled)
armpmu->start(armpmu);
}
@@ -549,6 +570,7 @@ static void armpmu_init(struct arm_pmu *armpmu)
.stop = armpmu_stop,
.read = armpmu_read,
.filter_match = armpmu_filter_match,
+ .events_across_hotplug = 1,
};
}
@@ -693,6 +715,44 @@ static int cpu_pmu_request_irq(struct arm_pmu *cpu_pmu, irq_handler_t handler)
return 0;
}
+static int cpu_has_active_perf(int cpu, struct arm_pmu *cpu_pmu)
+{
+ struct pmu_hw_events *hw_events;
+ int enabled;
+
+ if (!cpu_pmu)
+ return 0;
+ hw_events = cpu_pmu->hw_events;
+ enabled = bitmap_weight(hw_events->used_mask, cpu_pmu->num_events);
+
+ if (enabled)
+ /*Even one event's existence is good enough.*/
+ return 1;
+
+ return 0;
+}
+
+static void armpmu_update_counters(void *x)
+{
+ struct arm_pmu *cpu_pmu = (struct arm_pmu *)x;
+ struct pmu_hw_events *hw_events;
+ int idx;
+
+ if (!cpu_pmu)
+ return;
+
+ hw_events = cpu_pmu->hw_events;
+
+ for (idx = 0; idx <= cpu_pmu->num_events; ++idx) {
+ struct perf_event *event = hw_events->events[idx];
+
+ if (!event)
+ continue;
+
+ cpu_pmu->pmu.read(event);
+ }
+}
+
/*
* PMU hardware loses all context when a CPU goes offline.
* When a CPU is hotplugged back in, since some hardware registers are
@@ -703,22 +763,114 @@ static int cpu_pmu_notify(struct notifier_block *b, unsigned long action,
void *hcpu)
{
int cpu = (unsigned long)hcpu;
- struct arm_pmu *pmu = container_of(b, struct arm_pmu, hotplug_nb);
-
- if ((action & ~CPU_TASKS_FROZEN) != CPU_STARTING)
+ struct arm_pmu *cpu_pmu = container_of(b, struct arm_pmu, hotplug_nb);
+ int irq;
+ struct pmu *pmu;
+ int perf_running;
+ unsigned long masked_action = action & ~CPU_TASKS_FROZEN;
+ int ret = NOTIFY_DONE;
+
+ if ((masked_action != CPU_DOWN_PREPARE) &&
+ (masked_action != CPU_STARTING))
return NOTIFY_DONE;
- if (!cpumask_test_cpu(cpu, &pmu->supported_cpus))
- return NOTIFY_DONE;
+ if (masked_action == CPU_STARTING)
+ ret = NOTIFY_OK;
- if (pmu->reset)
- pmu->reset(pmu);
- else
+ if (!cpu_pmu)
+ return ret;
+
+ if (!cpumask_test_cpu(cpu, &cpu_pmu->supported_cpus))
return NOTIFY_DONE;
+ perf_running = atomic_read(&cpu_pmu->active_events);
+ switch (masked_action) {
+ case CPU_DOWN_PREPARE:
+ if (cpu_pmu->save_pm_registers)
+ smp_call_function_single(cpu,
+ cpu_pmu->save_pm_registers, hcpu, 1);
+ if (perf_running) {
+ if (cpu_has_active_perf(cpu, cpu_pmu))
+ smp_call_function_single(cpu,
+ armpmu_update_counters, cpu_pmu, 1);
+ /* Disarm the PMU IRQ before disappearing. */
+ if (cpu_pmu->plat_device) {
+ irq = cpu_pmu->percpu_irq;
+ smp_call_function_single(cpu,
+ cpu_pmu_disable_percpu_irq, &irq, 1);
+ }
+ }
+ break;
+
+ case CPU_STARTING:
+ /* Reset PMU to clear counters for ftrace buffer */
+ if (cpu_pmu->reset)
+ cpu_pmu->reset(NULL);
+ if (cpu_pmu->restore_pm_registers)
+ cpu_pmu->restore_pm_registers(hcpu);
+ if (perf_running) {
+ /* Arm the PMU IRQ before appearing. */
+ if (cpu_pmu->plat_device) {
+ irq = cpu_pmu->percpu_irq;
+ cpu_pmu_enable_percpu_irq(&irq);
+ }
+ if (cpu_has_active_perf(cpu, cpu_pmu)) {
+ get_cpu_var(from_idle) = 1;
+ pmu = &cpu_pmu->pmu;
+ pmu->pmu_enable(pmu);
+ }
+ }
+ break;
+ }
+ return ret;
+}
+
+static int perf_cpu_pm_notifier(struct notifier_block *self, unsigned long cmd,
+ void *v)
+{
+ struct arm_pmu *cpu_pmu = container_of(self, struct arm_pmu, hotplug_nb);
+ struct pmu *pmu;
+ u64 lcpu = smp_processor_id();
+ int cpu = (int)lcpu;
+
+ if (!cpu_pmu)
+ return NOTIFY_OK;
+
+ switch (cmd) {
+ case CPU_PM_ENTER:
+ if (cpu_pmu->save_pm_registers)
+ cpu_pmu->save_pm_registers((void *)lcpu);
+ if (cpu_has_active_perf(cpu, cpu_pmu)) {
+ armpmu_update_counters(cpu_pmu);
+ pmu = &cpu_pmu->pmu;
+ pmu->pmu_disable(pmu);
+ }
+ break;
+
+ case CPU_PM_ENTER_FAILED:
+ case CPU_PM_EXIT:
+ if (cpu_pmu->restore_pm_registers)
+ cpu_pmu->restore_pm_registers((void *)lcpu);
+ if (cpu_has_active_perf(cpu, cpu_pmu) && cpu_pmu->reset) {
+ /*
+ * Flip this bit so armpmu_enable knows it needs
+ * to re-enable active counters.
+ */
+ get_cpu_var(from_idle) = 1;
+ cpu_pmu->reset(NULL);
+ pmu = &cpu_pmu->pmu;
+ pmu->pmu_enable(pmu);
+ }
+ break;
+ }
+
return NOTIFY_OK;
}
+static struct notifier_block perf_cpu_pm_notifier_block = {
+ .notifier_call = perf_cpu_pm_notifier,
+};
+
static int cpu_pmu_init(struct arm_pmu *cpu_pmu)
{
int err;
@@ -734,6 +886,10 @@ static int cpu_pmu_init(struct arm_pmu *cpu_pmu)
if (err)
goto out_hw_events;
+ err = cpu_pm_register_notifier(&perf_cpu_pm_notifier_block);
+ if (err)
+ goto err_cpu_pm;
+
for_each_possible_cpu(cpu) {
struct pmu_hw_events *events = per_cpu_ptr(cpu_hw_events, cpu);
raw_spin_lock_init(&events->pmu_lock);
@@ -755,6 +911,8 @@ static int cpu_pmu_init(struct arm_pmu *cpu_pmu)
return 0;
+err_cpu_pm:
+ unregister_cpu_notifier(&cpu_pmu->hotplug_nb);
out_hw_events:
free_percpu(cpu_hw_events);
return err;
@@ -909,6 +1067,8 @@ int arm_pmu_device_probe(struct platform_device *pdev,
goto out_free;
}
+ pmu->percpu_irq = platform_get_irq(pmu->plat_device, 0);
+
ret = cpu_pmu_init(pmu);
if (ret)
goto out_free;
diff --git a/include/linux/perf/arm_pmu.h b/include/linux/perf/arm_pmu.h
index f7bbce527649..12456debc693 100644
--- a/include/linux/perf/arm_pmu.h
+++ b/include/linux/perf/arm_pmu.h
@@ -64,6 +64,8 @@ struct pmu_hw_events {
*/
DECLARE_BITMAP(used_mask, ARMPMU_MAX_HWEVENTS);
+ u32 *from_idle;
+
/*
* Hardware lock to serialize accesses to PMU registers. Needed for the
* read/modify/write sequences.
@@ -101,12 +103,15 @@ struct arm_pmu {
void (*free_irq)(struct arm_pmu *);
int (*map_event)(struct perf_event *event);
int num_events;
+ int percpu_irq;
atomic_t active_events;
struct mutex reserve_mutex;
u64 max_period;
struct platform_device *plat_device;
struct pmu_hw_events __percpu *hw_events;
struct notifier_block hotplug_nb;
+ void (*save_pm_registers)(void *hcpu);
+ void (*restore_pm_registers)(void *hcpu);
};
#define to_arm_pmu(p) (container_of(p, struct arm_pmu, pmu))