[RFC PATCH v4 2/2] PM: domains: Skip disabling unused until sync state

From: Abel Vesa
Date: Fri May 05 2023 - 11:03:06 EST


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@xxxxxxxxxx>
---
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))
#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)
{
--
2.34.1