Message ID | 20230505150241.3469424-3-abel.vesa@linaro.org (mailing list archive) |
---|---|
State | RFC, archived |
Headers | show |
Series | PM: domain: Support skiping disabling unused domains until sync state | expand |
On Fri, 5 May 2023 at 17:02, Abel Vesa <abel.vesa@linaro.org> wrote: > > By storing the status of the domain at boot, specified by the provider, > we can decide to skip powering 'off' the domain on the late initcall, > strictly based on the status boot being 'on' or 'unknown', and then > assume the provider will disable it from its sync state callback. > Also, provide a generic genpd sync state callback for those providers > that only need that when they state synced. If I understand correctly, this means that providers that don't use the sync state callback, will be kept powered-on until the late initcall, even if those could be turned off at an earlier point. Is this really what we want? > > Signed-off-by: Abel Vesa <abel.vesa@linaro.org> > --- > drivers/base/power/domain.c | 51 +++++++++++++++++++++++++++++++++++-- > include/linux/pm_domain.h | 5 ++++ > 2 files changed, 54 insertions(+), 2 deletions(-) > > diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c > index 33a3945c023e..9cc0ce43b47b 100644 > --- a/drivers/base/power/domain.c > +++ b/drivers/base/power/domain.c > @@ -125,6 +125,7 @@ static const struct genpd_lock_ops genpd_spin_ops = { > #define genpd_unlock(p) p->lock_ops->unlock(p) > > #define genpd_status_on(genpd) (genpd->status == GENPD_STATE_ON) > +#define genpd_boot_keep_on(genpd) (!(genpd->boot_status == GENPD_STATE_OFF)) This seems a bit unnecessarily complicated. Can't we just use bool in the genpd struct, to track whether we should allow powering off or not, based upon the boot condition. > #define genpd_is_irq_safe(genpd) (genpd->flags & GENPD_FLAG_IRQ_SAFE) > #define genpd_is_always_on(genpd) (genpd->flags & GENPD_FLAG_ALWAYS_ON) > #define genpd_is_active_wakeup(genpd) (genpd->flags & GENPD_FLAG_ACTIVE_WAKEUP) > @@ -654,6 +655,29 @@ static void genpd_queue_power_off_work(struct generic_pm_domain *genpd) > queue_work(pm_wq, &genpd->power_off_work); > } > > +/** > + * pm_genpd_power_off_unused_sync_state - Power off all domains for provider. > + * @dev: Provider's device. > + * > + * Request power off for all unused domains of the provider. > + * This should be used exclusively as sync state callback for genpd providers. > + */ > +void pm_genpd_power_off_unused_sync_state(struct device *dev) > +{ > + struct generic_pm_domain *genpd; > + > + mutex_lock(&gpd_list_lock); > + > + list_for_each_entry(genpd, &gpd_list, gpd_list_node) It looks like we need the of_genpd_mutex here as well, as it's really the list of providers that we want to protect too. > + if (genpd->provider->dev == dev && genpd_boot_keep_on(genpd)) { > + genpd->boot_status = GENPD_STATE_OFF; This needs to be done while holding the genpd's lock. > + genpd_queue_power_off_work(genpd); > + } > + > + mutex_unlock(&gpd_list_lock); > +} > +EXPORT_SYMBOL_GPL(pm_genpd_power_off_unused_sync_state); > + > /** > * genpd_power_off - Remove power from a given PM domain. > * @genpd: PM domain to power down. > @@ -674,6 +698,12 @@ static int genpd_power_off(struct generic_pm_domain *genpd, bool one_dev_on, > unsigned int not_suspended = 0; > int ret; > > + /* > + * If the domain was left enabled at boot stage, > + * abort power off until sync state is reached. > + */ > + if (genpd_boot_keep_on(genpd)) > + return -EBUSY; > /* > * Do not try to power off the domain in the following situations: > * (1) The domain is already in the "power off" state. > @@ -763,6 +793,12 @@ static int genpd_power_on(struct generic_pm_domain *genpd, unsigned int depth) > struct gpd_link *link; > int ret = 0; > > + /* > + * Just in case this is the first power on request, mark the boot > + * status of it as 'off'. > + */ > + genpd->boot_status = GENPD_STATE_OFF; > + > if (genpd_status_on(genpd)) > return 0; > > @@ -1095,8 +1131,16 @@ static int __init genpd_power_off_unused(void) > > mutex_lock(&gpd_list_lock); > > + /* > + * If the provider has registered a 'sync state' callback, > + * assume that callback will power off its registered unused domains, > + * otherwise we power them off from here. > + */ > list_for_each_entry(genpd, &gpd_list, gpd_list_node) > - genpd_queue_power_off_work(genpd); > + if (!dev_has_sync_state(&genpd->dev)) { The genpd->dev isn't really the one that is used by the genpd provider driver, hence this doesn't work. In the code you introduced above, you used the "genpd->provider->dev", which is probably what we want here too, right? > + genpd->boot_status = GENPD_STATE_OFF; Updating this needs to be protected by the genpd's lock. > + genpd_queue_power_off_work(genpd); > + } The above said - I am concerned about having to hold each genpd's lock in this path. I realize that we need to update the genpd->boot_status at some point, but let me think a bit if we can do that in a slightly better way, without holding all the locks. > > mutex_unlock(&gpd_list_lock); > > @@ -1124,6 +1168,9 @@ static void genpd_sync_power_off(struct generic_pm_domain *genpd, bool use_lock, > { > struct gpd_link *link; > > + if (genpd_boot_keep_on(genpd)) > + return; > + > if (!genpd_status_on(genpd) || genpd_is_always_on(genpd)) > return; > > @@ -2064,7 +2111,7 @@ int pm_genpd_init(struct generic_pm_domain *genpd, > genpd->gov = gov; > INIT_WORK(&genpd->power_off_work, genpd_power_off_work_fn); > atomic_set(&genpd->sd_count, 0); > - genpd->status = boot_status; > + genpd->status = genpd->boot_status = boot_status; > genpd->device_count = 0; > genpd->provider = NULL; > genpd->has_provider = false; > diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h > index c545e44ee52b..86bb531a319c 100644 > --- a/include/linux/pm_domain.h > +++ b/include/linux/pm_domain.h > @@ -132,6 +132,7 @@ struct generic_pm_domain { > const char *name; > atomic_t sd_count; /* Number of subdomains with power "on" */ > enum gpd_status status; /* Current state of the domain */ > + enum gpd_status boot_status; /* Boot state of the domain */ > unsigned int device_count; /* Number of devices */ > unsigned int suspended_count; /* System suspend device counter */ > unsigned int prepared_count; /* Suspend counter of prepared devices */ > @@ -233,6 +234,7 @@ int pm_genpd_init(struct generic_pm_domain *genpd, > struct dev_power_governor *gov, > enum gpd_status boot_status); > int pm_genpd_remove(struct generic_pm_domain *genpd); > +void pm_genpd_power_off_unused_sync_state(struct device *dev); > int dev_pm_genpd_set_performance_state(struct device *dev, unsigned int state); > int dev_pm_genpd_add_notifier(struct device *dev, struct notifier_block *nb); > int dev_pm_genpd_remove_notifier(struct device *dev); > @@ -281,6 +283,9 @@ static inline int pm_genpd_remove(struct generic_pm_domain *genpd) > return -EOPNOTSUPP; > } > > +static inline void pm_genpd_power_off_unused_sync_state(struct device *dev) > +{ } > + > static inline int dev_pm_genpd_set_performance_state(struct device *dev, > unsigned int state) > { Kind regards Uffe
diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 33a3945c023e..9cc0ce43b47b 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -125,6 +125,7 @@ static const struct genpd_lock_ops genpd_spin_ops = { #define genpd_unlock(p) p->lock_ops->unlock(p) #define genpd_status_on(genpd) (genpd->status == GENPD_STATE_ON) +#define genpd_boot_keep_on(genpd) (!(genpd->boot_status == GENPD_STATE_OFF)) #define genpd_is_irq_safe(genpd) (genpd->flags & GENPD_FLAG_IRQ_SAFE) #define genpd_is_always_on(genpd) (genpd->flags & GENPD_FLAG_ALWAYS_ON) #define genpd_is_active_wakeup(genpd) (genpd->flags & GENPD_FLAG_ACTIVE_WAKEUP) @@ -654,6 +655,29 @@ static void genpd_queue_power_off_work(struct generic_pm_domain *genpd) queue_work(pm_wq, &genpd->power_off_work); } +/** + * pm_genpd_power_off_unused_sync_state - Power off all domains for provider. + * @dev: Provider's device. + * + * Request power off for all unused domains of the provider. + * This should be used exclusively as sync state callback for genpd providers. + */ +void pm_genpd_power_off_unused_sync_state(struct device *dev) +{ + struct generic_pm_domain *genpd; + + mutex_lock(&gpd_list_lock); + + list_for_each_entry(genpd, &gpd_list, gpd_list_node) + if (genpd->provider->dev == dev && genpd_boot_keep_on(genpd)) { + genpd->boot_status = GENPD_STATE_OFF; + genpd_queue_power_off_work(genpd); + } + + mutex_unlock(&gpd_list_lock); +} +EXPORT_SYMBOL_GPL(pm_genpd_power_off_unused_sync_state); + /** * genpd_power_off - Remove power from a given PM domain. * @genpd: PM domain to power down. @@ -674,6 +698,12 @@ static int genpd_power_off(struct generic_pm_domain *genpd, bool one_dev_on, unsigned int not_suspended = 0; int ret; + /* + * If the domain was left enabled at boot stage, + * abort power off until sync state is reached. + */ + if (genpd_boot_keep_on(genpd)) + return -EBUSY; /* * Do not try to power off the domain in the following situations: * (1) The domain is already in the "power off" state. @@ -763,6 +793,12 @@ static int genpd_power_on(struct generic_pm_domain *genpd, unsigned int depth) struct gpd_link *link; int ret = 0; + /* + * Just in case this is the first power on request, mark the boot + * status of it as 'off'. + */ + genpd->boot_status = GENPD_STATE_OFF; + if (genpd_status_on(genpd)) return 0; @@ -1095,8 +1131,16 @@ static int __init genpd_power_off_unused(void) mutex_lock(&gpd_list_lock); + /* + * If the provider has registered a 'sync state' callback, + * assume that callback will power off its registered unused domains, + * otherwise we power them off from here. + */ list_for_each_entry(genpd, &gpd_list, gpd_list_node) - genpd_queue_power_off_work(genpd); + if (!dev_has_sync_state(&genpd->dev)) { + genpd->boot_status = GENPD_STATE_OFF; + genpd_queue_power_off_work(genpd); + } mutex_unlock(&gpd_list_lock); @@ -1124,6 +1168,9 @@ static void genpd_sync_power_off(struct generic_pm_domain *genpd, bool use_lock, { struct gpd_link *link; + if (genpd_boot_keep_on(genpd)) + return; + if (!genpd_status_on(genpd) || genpd_is_always_on(genpd)) return; @@ -2064,7 +2111,7 @@ int pm_genpd_init(struct generic_pm_domain *genpd, genpd->gov = gov; INIT_WORK(&genpd->power_off_work, genpd_power_off_work_fn); atomic_set(&genpd->sd_count, 0); - genpd->status = boot_status; + genpd->status = genpd->boot_status = boot_status; genpd->device_count = 0; genpd->provider = NULL; genpd->has_provider = false; diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index c545e44ee52b..86bb531a319c 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -132,6 +132,7 @@ struct generic_pm_domain { const char *name; atomic_t sd_count; /* Number of subdomains with power "on" */ enum gpd_status status; /* Current state of the domain */ + enum gpd_status boot_status; /* Boot state of the domain */ unsigned int device_count; /* Number of devices */ unsigned int suspended_count; /* System suspend device counter */ unsigned int prepared_count; /* Suspend counter of prepared devices */ @@ -233,6 +234,7 @@ int pm_genpd_init(struct generic_pm_domain *genpd, struct dev_power_governor *gov, enum gpd_status boot_status); int pm_genpd_remove(struct generic_pm_domain *genpd); +void pm_genpd_power_off_unused_sync_state(struct device *dev); int dev_pm_genpd_set_performance_state(struct device *dev, unsigned int state); int dev_pm_genpd_add_notifier(struct device *dev, struct notifier_block *nb); int dev_pm_genpd_remove_notifier(struct device *dev); @@ -281,6 +283,9 @@ static inline int pm_genpd_remove(struct generic_pm_domain *genpd) return -EOPNOTSUPP; } +static inline void pm_genpd_power_off_unused_sync_state(struct device *dev) +{ } + static inline int dev_pm_genpd_set_performance_state(struct device *dev, unsigned int state) {
By storing the status of the domain at boot, specified by the provider, we can decide to skip powering 'off' the domain on the late initcall, strictly based on the status boot being 'on' or 'unknown', and then assume the provider will disable it from its sync state callback. Also, provide a generic genpd sync state callback for those providers that only need that when they state synced. Signed-off-by: Abel Vesa <abel.vesa@linaro.org> --- drivers/base/power/domain.c | 51 +++++++++++++++++++++++++++++++++++-- include/linux/pm_domain.h | 5 ++++ 2 files changed, 54 insertions(+), 2 deletions(-)