[PATCH] drivers: perf: arm: implement CPU_PM notifier

Mathieu Poirier mathieu.poirier at linaro.org
Wed Feb 24 08:20:22 PST 2016


On 23 February 2016 at 11:22, Lorenzo Pieralisi
<lorenzo.pieralisi at arm.com> wrote:
> When a CPU is suspended (either through suspend-to-RAM or CPUidle),
> its PMU registers content can be lost, which means that counters
> registers values that were initialized on power down entry have to be
> reprogrammed on power-up to make sure the counters set-up is preserved
> (ie on power-up registers take the reset values on Cold or Warm reset,
> which can be architecturally UNKNOWN).
>
> To guarantee seamless profiling conditions across a core power down
> this patch adds a CPU PM notifier to ARM pmus, that upon CPU PM
> entry/exit from low-power states saves/restores the pmu registers
> set-up (by using the ARM perf API), so that the power-down/up cycle does
> not affect the perf behaviour (apart from a black-out period between
> power-up/down CPU PM notifications that is unavoidable).
>
> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi at arm.com>
> Cc: Ashwin Chaugule <ashwin.chaugule at linaro.org>
> Cc: Will Deacon <will.deacon at arm.com>
> Cc: Kevin Hilman <khilman at baylibre.com>
> Cc: Sudeep Holla <sudeep.holla at arm.com>
> Cc: Daniel Lezcano <daniel.lezcano at linaro.org>
> Cc: Mathieu Poirier <mathieu.poirier at linaro.org>
> Cc: Mark Rutland <mark.rutland at arm.com>
> ---
>  drivers/perf/arm_pmu.c       | 95 ++++++++++++++++++++++++++++++++++++++++++++
>  include/linux/perf/arm_pmu.h |  1 +
>  2 files changed, 96 insertions(+)
>
> diff --git a/drivers/perf/arm_pmu.c b/drivers/perf/arm_pmu.c
> index 166637f..12317a9 100644
> --- a/drivers/perf/arm_pmu.c
> +++ b/drivers/perf/arm_pmu.c
> @@ -13,6 +13,7 @@
>
>  #include <linux/bitmap.h>
>  #include <linux/cpumask.h>
> +#include <linux/cpu_pm.h>
>  #include <linux/export.h>
>  #include <linux/kernel.h>
>  #include <linux/of_device.h>
> @@ -710,6 +711,93 @@ static int cpu_pmu_notify(struct notifier_block *b, unsigned long action,
>         return NOTIFY_OK;
>  }
>
> +#ifdef CONFIG_CPU_PM
> +static void cpu_pm_pmu_setup(struct arm_pmu *armpmu, unsigned long cmd)
> +{
> +       struct pmu_hw_events *hw_events = this_cpu_ptr(armpmu->hw_events);
> +       struct perf_event *event;
> +       int idx;
> +
> +       for (idx = 0; idx < armpmu->num_events; idx++) {
> +               /*
> +                * If the counter is not used skip it, there is no
> +                * need of stopping/restarting it.
> +                */
> +               if (!test_bit(idx, hw_events->used_mask))
> +                       continue;
> +
> +               event = hw_events->events[idx];
> +
> +               switch (cmd) {
> +               case CPU_PM_ENTER:
> +                       /*
> +                        * Stop and update the counter
> +                        */
> +                       armpmu_stop(event, PERF_EF_UPDATE);
> +                       break;
> +               case CPU_PM_EXIT:
> +               case CPU_PM_ENTER_FAILED:
> +                        /* Restore and enable the counter */
> +                       armpmu_start(event, PERF_EF_RELOAD);
> +                       break;
> +               default:
> +                       break;
> +               }
> +       }
> +}
> +
> +static int cpu_pm_pmu_notify(struct notifier_block *b, unsigned long cmd,
> +                            void *v)
> +{
> +       struct arm_pmu *armpmu = container_of(b, struct arm_pmu, cpu_pm_nb);
> +       struct pmu_hw_events *hw_events = this_cpu_ptr(armpmu->hw_events);
> +       int enabled = bitmap_weight(hw_events->used_mask, armpmu->num_events);
> +
> +       if (!cpumask_test_cpu(smp_processor_id(), &armpmu->supported_cpus))
> +               return NOTIFY_DONE;
> +
> +       /*
> +        * Always reset the PMU registers on power-up even if
> +        * there are no events running.
> +        */
> +       if (cmd == CPU_PM_EXIT && armpmu->reset)
> +               armpmu->reset(armpmu);

I think this patch does the right thing but I can't get the above
reset.  Wouldn't it be better to do the reset as part of the
CPU_PM_EXIT case below?  At this point nothing tells us the CPU won't
go back down before the event is enabled, wasting the cycle needed to
reset the PMU.

Thanks,
Mathieu

> +
> +       if (!enabled)
> +               return NOTIFY_OK;
> +
> +       switch (cmd) {
> +       case CPU_PM_ENTER:
> +               armpmu->stop(armpmu);
> +               cpu_pm_pmu_setup(armpmu, cmd);
> +               break;
> +       case CPU_PM_EXIT:
> +               cpu_pm_pmu_setup(armpmu, cmd);
> +       case CPU_PM_ENTER_FAILED:
> +               armpmu->start(armpmu);
> +               break;
> +       default:
> +               return NOTIFY_DONE;
> +       }
> +
> +       return NOTIFY_OK;
> +}
> +
> +static int cpu_pm_pmu_register(struct arm_pmu *cpu_pmu)
> +{
> +       cpu_pmu->cpu_pm_nb.notifier_call = cpu_pm_pmu_notify;
> +       return cpu_pm_register_notifier(&cpu_pmu->cpu_pm_nb);
> +}
> +
> +static void cpu_pm_pmu_unregister(struct arm_pmu *cpu_pmu)
> +{
> +       cpu_pm_unregister_notifier(&cpu_pmu->cpu_pm_nb);
> +}
> +#else
> +static inline int cpu_pm_pmu_register(struct arm_pmu *cpu_pmu) { return 0; }
> +static inline void cpu_pm_pmu_unregister(struct arm_pmu *cpu_pmu) { }
> +#endif
> +
>  static int cpu_pmu_init(struct arm_pmu *cpu_pmu)
>  {
>         int err;
> @@ -725,6 +813,10 @@ static int cpu_pmu_init(struct arm_pmu *cpu_pmu)
>         if (err)
>                 goto out_hw_events;
>
> +       err = cpu_pm_pmu_register(cpu_pmu);
> +       if (err)
> +               goto out_unregister;
> +
>         for_each_possible_cpu(cpu) {
>                 struct pmu_hw_events *events = per_cpu_ptr(cpu_hw_events, cpu);
>                 raw_spin_lock_init(&events->pmu_lock);
> @@ -746,6 +838,8 @@ static int cpu_pmu_init(struct arm_pmu *cpu_pmu)
>
>         return 0;
>
> +out_unregister:
> +       unregister_cpu_notifier(&cpu_pmu->hotplug_nb);
>  out_hw_events:
>         free_percpu(cpu_hw_events);
>         return err;
> @@ -753,6 +847,7 @@ out_hw_events:
>
>  static void cpu_pmu_destroy(struct arm_pmu *cpu_pmu)
>  {
> +       cpu_pm_pmu_unregister(cpu_pmu);
>         unregister_cpu_notifier(&cpu_pmu->hotplug_nb);
>         free_percpu(cpu_pmu->hw_events);
>  }
> diff --git a/include/linux/perf/arm_pmu.h b/include/linux/perf/arm_pmu.h
> index 83b5e34..9ffa316 100644
> --- a/include/linux/perf/arm_pmu.h
> +++ b/include/linux/perf/arm_pmu.h
> @@ -107,6 +107,7 @@ struct arm_pmu {
>         struct platform_device  *plat_device;
>         struct pmu_hw_events    __percpu *hw_events;
>         struct notifier_block   hotplug_nb;
> +       struct notifier_block   cpu_pm_nb;
>  };
>
>  #define to_arm_pmu(p) (container_of(p, struct arm_pmu, pmu))
> --
> 2.5.1
>



More information about the linux-arm-kernel mailing list