[PATCH v2 7/8] PM / Domains: Support IRQ safe PM domains

Ulf Hansson ulf.hansson at linaro.org
Mon Oct 10 04:04:58 PDT 2016


On 8 October 2016 at 00:37, Lina Iyer <lina.iyer at linaro.org> wrote:
> Generic Power Domains currently support turning on/off only in process
> context. This prevents the usage of PM domains for domains that could be
> powered on/off in a context where IRQs are disabled. Many such domains
> exist today and do not get powered off, when the IRQ safe devices in
> that domain are powered off, because of this limitation.
>
> However, not all domains can operate in IRQ safe contexts. Genpd
> therefore, has to support both cases where the domain may or may not
> operate in IRQ safe contexts. Configuring genpd to use an appropriate
> lock for that domain, would allow domains that have IRQ safe devices to
> runtime suspend and resume, in atomic context.
>
> To achieve domain specific locking, set the domain's ->flag to
> GENPD_FLAG_IRQ_SAFE while defining the domain. This indicates that genpd
> should use a spinlock instead of a mutex for locking the domain. Locking
> is abstracted through genpd_lock() and genpd_unlock() functions that use
> the flag to determine the appropriate lock to be used for that domain.
>
> Domains that have lower latency to suspend and resume and can operate
> with IRQs disabled may now be able to save power, when the component
> devices and sub-domains are idle at runtime.
>
> The restriction this imposes on the domain hierarchy is that non-IRQ
> safe domains may not have IRQ-safe subdomains, but IRQ safe domains may
> have IRQ safe and non-IRQ safe subdomains and devices.
>
> Cc: Ulf Hansson <ulf.hansson at linaro.org>
> Cc: Kevin Hilman <khilman at kernel.org>
> Cc: Rafael J. Wysocki <rjw at rjwysocki.net>
> Signed-off-by: Lina Iyer <lina.iyer at linaro.org>

Acked-by: Ulf Hansson <ulf.hansson at linaro.org>

Kind regards
Uffe

> ---
>  drivers/base/power/domain.c | 111 ++++++++++++++++++++++++++++++++++++++++----
>  include/linux/pm_domain.h   |  10 +++-
>  2 files changed, 110 insertions(+), 11 deletions(-)
>
> diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c
> index d0ae559..87a016a 100644
> --- a/drivers/base/power/domain.c
> +++ b/drivers/base/power/domain.c
> @@ -74,11 +74,70 @@ static const struct genpd_lock_ops genpd_mtx_ops = {
>         .unlock = genpd_unlock_mtx,
>  };
>
> +static void genpd_lock_spin(struct generic_pm_domain *genpd)
> +       __acquires(&genpd->slock)
> +{
> +       unsigned long flags;
> +
> +       spin_lock_irqsave(&genpd->slock, flags);
> +       genpd->lock_flags = flags;
> +}
> +
> +static void genpd_lock_nested_spin(struct generic_pm_domain *genpd,
> +                                       int depth)
> +       __acquires(&genpd->slock)
> +{
> +       unsigned long flags;
> +
> +       spin_lock_irqsave_nested(&genpd->slock, flags, depth);
> +       genpd->lock_flags = flags;
> +}
> +
> +static int genpd_lock_interruptible_spin(struct generic_pm_domain *genpd)
> +       __acquires(&genpd->slock)
> +{
> +       unsigned long flags;
> +
> +       spin_lock_irqsave(&genpd->slock, flags);
> +       genpd->lock_flags = flags;
> +       return 0;
> +}
> +
> +static void genpd_unlock_spin(struct generic_pm_domain *genpd)
> +       __releases(&genpd->slock)
> +{
> +       spin_unlock_irqrestore(&genpd->slock, genpd->lock_flags);
> +}
> +
> +static const struct genpd_lock_ops genpd_spin_ops = {
> +       .lock = genpd_lock_spin,
> +       .lock_nested = genpd_lock_nested_spin,
> +       .lock_interruptible = genpd_lock_interruptible_spin,
> +       .unlock = genpd_unlock_spin,
> +};
> +
>  #define genpd_lock(p)                  p->lock_ops->lock(p)
>  #define genpd_lock_nested(p, d)                p->lock_ops->lock_nested(p, d)
>  #define genpd_lock_interruptible(p)    p->lock_ops->lock_interruptible(p)
>  #define genpd_unlock(p)                        p->lock_ops->unlock(p)
>
> +#define genpd_is_irq_safe(genpd)       (genpd->flags & GENPD_FLAG_IRQ_SAFE)
> +
> +static inline bool irq_safe_dev_in_no_sleep_domain(struct device *dev,
> +               struct generic_pm_domain *genpd)
> +{
> +       bool ret;
> +
> +       ret = pm_runtime_is_irq_safe(dev) && !genpd_is_irq_safe(genpd);
> +
> +       /* Warn once for each IRQ safe dev in no sleep domain */
> +       if (ret)
> +               dev_warn_once(dev, "PM domain %s will not be powered off\n",
> +                               genpd->name);
> +
> +       return ret;
> +}
> +
>  /*
>   * Get the generic PM domain for a particular struct device.
>   * This validates the struct device pointer, the PM domain pointer,
> @@ -343,7 +402,12 @@ static int genpd_poweroff(struct generic_pm_domain *genpd, bool is_async)
>                 if (stat > PM_QOS_FLAGS_NONE)
>                         return -EBUSY;
>
> -               if (!pm_runtime_suspended(pdd->dev) || pdd->dev->power.irq_safe)
> +               /*
> +                * Do not allow PM domain to be powered off, when an IRQ safe
> +                * device is part of a non-IRQ safe domain.
> +                */
> +               if (!pm_runtime_suspended(pdd->dev) ||
> +                       irq_safe_dev_in_no_sleep_domain(pdd->dev, genpd))
>                         not_suspended++;
>         }
>
> @@ -506,10 +570,10 @@ static int genpd_runtime_suspend(struct device *dev)
>         }
>
>         /*
> -        * If power.irq_safe is set, this routine will be run with interrupts
> -        * off, so it can't use mutexes.
> +        * If power.irq_safe is set, this routine may be run with
> +        * IRQs disabled, so suspend only if the PM domain also is irq_safe.
>          */
> -       if (dev->power.irq_safe)
> +       if (irq_safe_dev_in_no_sleep_domain(dev, genpd))
>                 return 0;
>
>         genpd_lock(genpd);
> @@ -543,8 +607,11 @@ static int genpd_runtime_resume(struct device *dev)
>         if (IS_ERR(genpd))
>                 return -EINVAL;
>
> -       /* If power.irq_safe, the PM domain is never powered off. */
> -       if (dev->power.irq_safe) {
> +       /*
> +        * As we don't power off a non IRQ safe domain, which holds
> +        * an IRQ safe device, we don't need to restore power to it.
> +        */
> +       if (irq_safe_dev_in_no_sleep_domain(dev, genpd)) {
>                 timed = false;
>                 goto out;
>         }
> @@ -586,7 +653,8 @@ static int genpd_runtime_resume(struct device *dev)
>  err_stop:
>         genpd_stop_dev(genpd, dev);
>  err_poweroff:
> -       if (!dev->power.irq_safe) {
> +       if (!pm_runtime_is_irq_safe(dev) ||
> +               (pm_runtime_is_irq_safe(dev) && genpd_is_irq_safe(genpd))) {
>                 genpd_lock(genpd);
>                 genpd_poweroff(genpd, 0);
>                 genpd_unlock(genpd);
> @@ -1223,6 +1291,17 @@ static int genpd_add_subdomain(struct generic_pm_domain *genpd,
>             || genpd == subdomain)
>                 return -EINVAL;
>
> +       /*
> +        * If the domain can be powered on/off in an IRQ safe
> +        * context, ensure that the subdomain can also be
> +        * powered on/off in that context.
> +        */
> +       if (!genpd_is_irq_safe(genpd) && genpd_is_irq_safe(subdomain)) {
> +               WARN("Parent %s of subdomain %s must be IRQ safe\n",
> +                               genpd->name, subdomain->name);
> +               return -EINVAL;
> +       }
> +
>         link = kzalloc(sizeof(*link), GFP_KERNEL);
>         if (!link)
>                 return -ENOMEM;
> @@ -1337,6 +1416,17 @@ static int genpd_set_default_power_state(struct generic_pm_domain *genpd)
>         return 0;
>  }
>
> +static void genpd_lock_init(struct generic_pm_domain *genpd)
> +{
> +       if (genpd->flags & GENPD_FLAG_IRQ_SAFE) {
> +               spin_lock_init(&genpd->slock);
> +               genpd->lock_ops = &genpd_spin_ops;
> +       } else {
> +               mutex_init(&genpd->mlock);
> +               genpd->lock_ops = &genpd_mtx_ops;
> +       }
> +}
> +
>  /**
>   * pm_genpd_init - Initialize a generic I/O PM domain object.
>   * @genpd: PM domain object to initialize.
> @@ -1356,8 +1446,7 @@ int pm_genpd_init(struct generic_pm_domain *genpd,
>         INIT_LIST_HEAD(&genpd->master_links);
>         INIT_LIST_HEAD(&genpd->slave_links);
>         INIT_LIST_HEAD(&genpd->dev_list);
> -       mutex_init(&genpd->mlock);
> -       genpd->lock_ops = &genpd_mtx_ops;
> +       genpd_lock_init(genpd);
>         genpd->gov = gov;
>         INIT_WORK(&genpd->power_off_work, genpd_power_off_work_fn);
>         atomic_set(&genpd->sd_count, 0);
> @@ -2133,7 +2222,9 @@ static int pm_genpd_summary_one(struct seq_file *s,
>         }
>
>         list_for_each_entry(pm_data, &genpd->dev_list, list_node) {
> -               kobj_path = kobject_get_path(&pm_data->dev->kobj, GFP_KERNEL);
> +               kobj_path = kobject_get_path(&pm_data->dev->kobj,
> +                               genpd_is_irq_safe(genpd) ?
> +                               GFP_ATOMIC : GFP_KERNEL);
>                 if (kobj_path == NULL)
>                         continue;
>
> diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h
> index 811b968..81ece61 100644
> --- a/include/linux/pm_domain.h
> +++ b/include/linux/pm_domain.h
> @@ -15,9 +15,11 @@
>  #include <linux/err.h>
>  #include <linux/of.h>
>  #include <linux/notifier.h>
> +#include <linux/spinlock.h>
>
>  /* Defines used for the flags field in the struct generic_pm_domain */
>  #define GENPD_FLAG_PM_CLK      (1U << 0) /* PM domain uses PM clk */
> +#define GENPD_FLAG_IRQ_SAFE    (1U << 1) /* PM domain operates in atomic */
>
>  enum gpd_status {
>         GPD_STATE_ACTIVE = 0,   /* PM domain is active */
> @@ -76,7 +78,13 @@ struct generic_pm_domain {
>         unsigned int state_idx; /* state that genpd will go to when off */
>         void *free; /* Free the state that was allocated for default */
>         const struct genpd_lock_ops *lock_ops;
> -       struct mutex mlock;
> +       union {
> +               struct mutex mlock;
> +               struct {
> +                       spinlock_t slock;
> +                       unsigned long lock_flags;
> +               };
> +       };
>
>  };
>
> --
> 2.7.4
>



More information about the linux-arm-kernel mailing list