[PATCH 6/7] PM / Domains: Add default power off governor function (v3)

Rafael J. Wysocki
2011-11-07T00:08:40Z

Add a function deciding whether or not a given PM domain should
be powered off on the basis of the PM QoS constraints of devices
belonging to it.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
drivers/base/power/domain.c | 2
drivers/base/power/domain_governor.c | 125 +++++++++++++++++++++++++++++++++++
include/linux/pm_domain.h | 9 ++
3 files changed, 136 insertions(+)

Index: linux/include/linux/pm_domain.h
===================================================================
--- linux.orig/include/linux/pm_domain.h
+++ linux/include/linux/pm_domain.h
@@ -57,8 +57,13 @@ struct generic_pm_domain {
bool suspend_power_off; /* Power status before system suspend */
bool dev_irq_safe; /* Device callbacks are IRQ-safe */
int (*power_off)(struct generic_pm_domain *domain);
+ s64 power_off_latency_ns;
int (*power_on)(struct generic_pm_domain *domain);
+ s64 power_on_latency_ns;
struct gpd_dev_ops dev_ops;
+ s64 break_even_ns; /* Power break even for the entire domain. */
+ s64 max_off_time_ns; /* Maximum allowed "suspended" time. */
+ ktime_t power_off_time;
};

static inline struct generic_pm_domain *pd_to_genpd(struct dev_pm_domain *pd)
@@ -74,6 +79,9 @@ struct gpd_link {
};

struct gpd_timing_data {
+ s64 start_latency_ns;
+ s64 save_state_latency_ns;
+ s64 restore_state_latency_ns;
s64 break_even_ns;
};

@@ -81,6 +89,7 @@ struct generic_pm_domain_data {
struct pm_domain_data base;
struct gpd_dev_ops ops;
struct gpd_timing_data td;
+ ktime_t stop_time;
bool need_restore;
};

Index: linux/drivers/base/power/domain_governor.c
===================================================================
--- linux.orig/drivers/base/power/domain_governor.c
+++ linux/drivers/base/power/domain_governor.c
@@ -10,6 +10,7 @@
#include <linux/kernel.h>
#include <linux/pm_domain.h>
#include <linux/pm_qos.h>
+#include <linux/hrtimer.h>

/**
* default_stop_ok - Default PM domain governor routine for stopping devices.
@@ -34,6 +35,130 @@ bool default_stop_ok(struct device *dev)
return constraint_ns > dev_gpd_data(dev)->td.break_even_ns;
}

+/**
+ * default_power_down_ok - Default generic PM domain power off governor routine.
+ * @pd: PM domain to check.
+ *
+ * This routine must be executed under the PM domain's lock.
+ */
+static bool default_power_down_ok(struct dev_pm_domain *pd)
+{
+ struct generic_pm_domain *genpd = pd_to_genpd(pd);
+ struct gpd_link *link;
+ struct pm_domain_data *pdd;
+ s64 delta_ns, min_delta_ns;
+ s64 max_save_time_ns;
+ ktime_t time_now = ktime_get();
+
+ /*
+ * Check if subdomains can be off for enough time.
+ *
+ * All subdomains have been powered off already at this point.
+ */
+ delta_ns = genpd->power_off_latency_ns + genpd->power_on_latency_ns;
+ list_for_each_entry(link, &genpd->master_links, master_node) {
+ struct generic_pm_domain *sd = link->slave;
+ s64 sd_max_off_ns = sd->max_off_time_ns;
+
+ if (sd_max_off_ns < 0)
+ continue;
+
+ sd_max_off_ns -= ktime_to_ns(ktime_sub(time_now,
+ sd->power_off_time));
+ /*
+ * Check if the subdomain is allowed to be off long enough for
+ * the current domain to turn off and on (that's how much time
+ * it will have to wait worst case).
+ */
+ if (sd_max_off_ns <= delta_ns)
+ return false;
+ }
+
+ /*
+ * If the "domain power off" operation is aborted, it may be necessary
+ * to wait for the slowest "save device state operation to complete.
+ *
+ * All devices in this domain have been stopped already at this point.
+ */
+ max_save_time_ns = 0;
+ list_for_each_entry(pdd, &genpd->dev_list, list_node) {
+ s64 save_time_ns;
+
+ if (!pdd->dev->driver)
+ continue;
+
+ save_time_ns = to_gpd_data(pdd)->td.save_state_latency_ns;
+ if (save_time_ns > max_save_time_ns)
+ max_save_time_ns = save_time_ns;
+ }
+
+ /*
+ * For each device in the domain compute the difference between the
+ * QoS latency constraint and the time already spent in the "stopped"
+ * state plus the total time required to bring the device back to the
+ * full power state (after the domain has been turned on). Compute the
+ * minimum of these values.
+ */
+ min_delta_ns = -1;
+ list_for_each_entry(pdd, &genpd->dev_list, list_node) {
+ struct generic_pm_domain_data *gpd_data;
+ struct device *dev = pdd->dev;
+ s32 constraint;
+
+ if (!dev->driver)
+ continue;
+
+ constraint = dev_pm_qos_read_value(dev);
+ if (constraint < 0) /* negative means "never stop" */
+ return false;
+ else if (constraint == 0) /* 0 means "don't care" */
+ continue;
+
+ delta_ns = constraint;
+ delta_ns *= NSEC_PER_USEC;
+
+ gpd_data = to_gpd_data(pdd);
+ delta_ns -= gpd_data->td.start_latency_ns +
+ ktime_to_ns(ktime_sub(time_now, gpd_data->stop_time));
+ if (delta_ns <= max_save_time_ns)
+ return false;
+
+ delta_ns -= gpd_data->td.restore_state_latency_ns;
+ if (delta_ns <= 0)
+ return false;
+
+ if (delta_ns < min_delta_ns)
+ min_delta_ns = delta_ns;
+ }
+
+ if (min_delta_ns < 0) {
+ /*
+ * There are no latency constraints, so the domain can spend
+ * arbitrary time in the "off" state.
+ */
+ genpd->max_off_time_ns = -1;
+ return true;
+ }
+
+ /*
+ * The difference between the computed minimum delta and the time needed
+ * to turn the domain on is the maximum theoretical time this domain can
+ * spend in the "off" state.
+ */
+ min_delta_ns -= genpd->power_on_latency_ns;
+ genpd->max_off_time_ns = min_delta_ns;
+
+ /*
+ * If the difference between the computed minimum delta and the time
+ * needed to turn the domain off and back on on is smaller than the
+ * domain's power break even time, removing power from the domain is not
+ * worth it.
+ */
+ min_delta_ns -= genpd->power_off_latency_ns;
+ return min_delta_ns > genpd->break_even_ns;
+}
+
struct dev_power_governor simple_qos_governor = {
.stop_ok = default_stop_ok,
+ .power_down_ok = default_power_down_ok,
};
Index: linux/drivers/base/power/domain.c
===================================================================
--- linux.orig/drivers/base/power/domain.c
+++ linux/drivers/base/power/domain.c
@@ -47,6 +47,7 @@ struct generic_pm_domain *dev_to_genpd(s

static int genpd_stop_dev(struct generic_pm_domain *genpd, struct device *dev)
{
+ dev_gpd_data(dev)->stop_time = ktime_get();
return GENPD_DEV_CALLBACK(genpd, int, stop, dev);
}

@@ -397,6 +398,7 @@ static int pm_genpd_poweroff(struct gene
}

genpd->status = GPD_STATE_POWER_OFF;
+ genpd->power_off_time = ktime_get();

list_for_each_entry(link, &genpd->slave_links, slave_node) {
genpd_sd_counter_dec(link->master);