diff options
author | Sudeep KarkadaNagesha <sudeep.karkadanagesha@arm.com> | 2012-08-30 15:46:15 +0100 |
---|---|---|
committer | Viresh Kumar <viresh.kumar@linaro.org> | 2012-10-03 15:26:33 +0530 |
commit | b3a41980c32b8e75eedc93cd12b94ddb66617cd5 (patch) | |
tree | e6b717ab9f377ebe0109c2fe8f9534b92342db34 | |
parent | c3cfbcd0ef0ae229bbdb6cf07b70337329bf10ff (diff) |
ARM: perf: save/restore pmu registers in pm notifier
This adds core support for saving and restoring CPU PMU registers
for suspend/resume support i.e. deeper C-states in cpuidle terms.
This patch adds support only to ARMv7 PMU registers save/restore.
It needs to be extended to xscale and ARMv6 if needed.
Signed-off-by: Sudeep KarkadaNagesha <sudeep.karkadanagesha@arm.com>
-rw-r--r-- | arch/arm/include/asm/pmu.h | 11 | ||||
-rw-r--r-- | arch/arm/kernel/perf_event_cpu.c | 27 | ||||
-rw-r--r-- | arch/arm/kernel/perf_event_v7.c | 47 |
3 files changed, 84 insertions, 1 deletions
diff --git a/arch/arm/include/asm/pmu.h b/arch/arm/include/asm/pmu.h index 95480a857f11..4bb9cac60794 100644 --- a/arch/arm/include/asm/pmu.h +++ b/arch/arm/include/asm/pmu.h @@ -62,6 +62,15 @@ struct pmu_hw_events { raw_spinlock_t pmu_lock; }; +struct cpupmu_regs { + u32 pmc; + u32 pmcntenset; + u32 pmuseren; + u32 pmintenset; + u32 pmxevttype[8]; + u32 pmxevtcnt[8]; +}; + struct arm_pmu { struct pmu pmu; cpumask_t active_irqs; @@ -82,6 +91,8 @@ struct arm_pmu { int (*request_irq)(struct arm_pmu *, irq_handler_t handler); void (*free_irq)(struct arm_pmu *); int (*map_event)(struct perf_event *event); + void (*save_regs)(struct arm_pmu *, struct cpupmu_regs *); + void (*restore_regs)(struct arm_pmu *, struct cpupmu_regs *); int num_events; atomic_t active_events; struct mutex reserve_mutex; diff --git a/arch/arm/kernel/perf_event_cpu.c b/arch/arm/kernel/perf_event_cpu.c index edd77f9da8b4..da2bfb32e275 100644 --- a/arch/arm/kernel/perf_event_cpu.c +++ b/arch/arm/kernel/perf_event_cpu.c @@ -19,6 +19,7 @@ #define pr_fmt(fmt) "CPU PMU: " fmt #include <linux/bitmap.h> +#include <linux/cpu_pm.h> #include <linux/export.h> #include <linux/kernel.h> #include <linux/of.h> @@ -35,6 +36,7 @@ static DEFINE_PER_CPU(unsigned long [BITS_TO_LONGS(ARMPMU_MAX_HWEVENTS)], used_m static DEFINE_PER_CPU(struct pmu_hw_events, cpu_hw_events); static DEFINE_PER_CPU(struct arm_pmu *, armcpu_pmu); static LIST_HEAD(cpupmu_list); +static DEFINE_PER_CPU(struct cpupmu_regs, cpu_pmu_regs); /* * Despite the names, these two functions are CPU-specific and are used @@ -167,10 +169,31 @@ static int __cpuinit cpu_pmu_notify(struct notifier_block *b, return NOTIFY_OK; } +static int cpu_pmu_pm_notify(struct notifier_block *b, + unsigned long action, void *hcpu) +{ + int cpu = smp_processor_id(); + struct arm_pmu *cpu_pmu = per_cpu(armcpu_pmu, cpu); + struct cpupmu_regs *pmuregs = &per_cpu(cpu_pmu_regs, cpu); + + if (action == CPU_PM_ENTER && cpu_pmu->save_regs) { + cpu_pmu->save_regs(cpu_pmu, pmuregs); + cpu_pmu->reset(cpu_pmu); + } else if (action == CPU_PM_EXIT && cpu_pmu->restore_regs) { + cpu_pmu->restore_regs(cpu_pmu, pmuregs); + } + + return NOTIFY_OK; +} + static struct notifier_block __cpuinitdata cpu_pmu_hotplug_notifier = { .notifier_call = cpu_pmu_notify, }; +static struct notifier_block __cpuinitdata cpu_pmu_pm_notifier = { + .notifier_call = cpu_pmu_pm_notify, +}; + int cpu_pmu_register(struct cpu_pmu_info *cpupmu_info) { if (!strlen(cpupmu_info->compatible) || !cpupmu_info->init @@ -302,8 +325,10 @@ static int __devinit cpu_pmu_device_probe(struct platform_device *pdev) cpupmu->plat_device = pdev; cpu_pmu_init(cpupmu); - if (!cluster) + if (!cluster) { register_cpu_notifier(&cpu_pmu_hotplug_notifier); + cpu_pm_register_notifier(&cpu_pmu_pm_notifier); + } armpmu_register(cpupmu, cpupmu->name, PERF_TYPE_RAW); return 0; diff --git a/arch/arm/kernel/perf_event_v7.c b/arch/arm/kernel/perf_event_v7.c index c8fe8ec5882d..9314fa986f09 100644 --- a/arch/arm/kernel/perf_event_v7.c +++ b/arch/arm/kernel/perf_event_v7.c @@ -959,6 +959,51 @@ static void armv7_pmnc_dump_regs(struct arm_pmu *cpu_pmu) } #endif +static void armv7_save_pmu_regs(struct arm_pmu *cpu_pmu, + struct cpupmu_regs *regs) +{ + unsigned int cnt; + asm volatile("mrc p15, 0, %0, c9, c12, 0" : "=r" (regs->pmc)); + if (!(regs->pmc & ARMV7_PMNC_E)) + return; + + asm volatile("mrc p15, 0, %0, c9, c12, 1" : "=r" (regs->pmcntenset)); + asm volatile("mrc p15, 0, %0, c9, c14, 0" : "=r" (regs->pmuseren)); + asm volatile("mrc p15, 0, %0, c9, c14, 1" : "=r" (regs->pmintenset)); + asm volatile("mrc p15, 0, %0, c9, c13, 0" : "=r" (regs->pmxevtcnt[0])); + for (cnt = ARMV7_IDX_COUNTER0; + cnt <= ARMV7_IDX_COUNTER_LAST(cpu_pmu); cnt++) { + armv7_pmnc_select_counter(cnt); + asm volatile("mrc p15, 0, %0, c9, c13, 1" + : "=r"(regs->pmxevttype[cnt])); + asm volatile("mrc p15, 0, %0, c9, c13, 2" + : "=r"(regs->pmxevtcnt[cnt])); + } + return; +} + +static void armv7_restore_pmu_regs(struct arm_pmu *cpu_pmu, + struct cpupmu_regs *regs) +{ + unsigned int cnt; + if (!(regs->pmc & ARMV7_PMNC_E)) + return; + + asm volatile("mcr p15, 0, %0, c9, c12, 1" : : "r" (regs->pmcntenset)); + asm volatile("mcr p15, 0, %0, c9, c14, 0" : : "r" (regs->pmuseren)); + asm volatile("mcr p15, 0, %0, c9, c14, 1" : : "r" (regs->pmintenset)); + asm volatile("mcr p15, 0, %0, c9, c13, 0" : : "r" (regs->pmxevtcnt[0])); + for (cnt = ARMV7_IDX_COUNTER0; + cnt <= ARMV7_IDX_COUNTER_LAST(cpu_pmu); cnt++) { + armv7_pmnc_select_counter(cnt); + asm volatile("mcr p15, 0, %0, c9, c13, 1" + : : "r"(regs->pmxevttype[cnt])); + asm volatile("mcr p15, 0, %0, c9, c13, 2" + : : "r"(regs->pmxevtcnt[cnt])); + } + asm volatile("mcr p15, 0, %0, c9, c12, 0" : : "r" (regs->pmc)); +} + static void armv7pmu_enable_event(struct perf_event *event) { unsigned long flags; @@ -1232,6 +1277,8 @@ static void armv7pmu_init(struct arm_pmu *cpu_pmu) cpu_pmu->start = armv7pmu_start; cpu_pmu->stop = armv7pmu_stop; cpu_pmu->reset = armv7pmu_reset; + cpu_pmu->save_regs = armv7_save_pmu_regs; + cpu_pmu->restore_regs = armv7_restore_pmu_regs; cpu_pmu->max_period = (1LLU << 32) - 1; }; |