diff options
author | Maciej Matraszek <m.matraszek@samsung.com> | 2014-09-15 13:09:10 +0200 |
---|---|---|
committer | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2014-09-23 23:16:58 +0200 |
commit | 2bd5306a8764d9496f3e3d90c4e608c247fcfd31 (patch) | |
tree | 9e93a607371760f3f594a9e764982d45df93696a /drivers/base/power/domain.c | |
parent | 91d66cd27f5fd8a3bca4621a3505c9067925478d (diff) | |
download | lwn-2bd5306a8764d9496f3e3d90c4e608c247fcfd31.tar.gz lwn-2bd5306a8764d9496f3e3d90c4e608c247fcfd31.zip |
PM / Domains: add debugfs listing of struct generic_pm_domain-s
Add /sys/kernel/debug/pm_genpd/pm_genpd_summary file, which
lists power domains in the system, their statuses and attached devices,
resembling /sys/kernel/debug/clk/clk_summary.
Currently it is impossible to inspect (from userland) whether
a power domain is on or off. And, if it is on, which device blocks it
from powering down. This change allows developers working on
embedded devices power efficiency to list all necessary information
about generic power domains in one place.
The content of pm_genpd/pm_genpd_summary file is generated by iterating
over all generic power domain in the system, and, for each,
over registered devices and over the subdomains, if present.
Example output:
$ cat /sys/kernel/debug/pm_genpd/pm_genpd_summary
domain status slaves
/device runtime status
----------------------------------------------------------------------
a4su off
a3sg off
a3sm on
a3sp on
/devices/e6600000.pwm suspended
/devices/e6c50000.serial active
/devices/e6850000.sd suspended
/devices/e6bd0000.mmc active
a4s on a3sp, a3sm, a3sg
/devices/e6900000.irqpin unsupported
/devices/e6900004.irqpin unsupported
/devices/e6900008.irqpin unsupported
/devices/e690000c.irqpin unsupported
/devices/e9a00000.ethernet active
a3rv off
a4r off a3rv
/devices/fff20000.i2c suspended
a4lc off
c5 on a4lc, a4r, a4s, a4su
/devices/e6050000.pfc unsupported
/devices/e6138000.timer active
To enable this feature, compile the kernel with debugfs
and CONFIG_PM_ADVANCED_DEBUG enabled.
Signed-off-by: Maciej Matraszek <m.matraszek@samsung.com>
Tested-by: Geert Uytterhoeven <geert+renesas@glider.be>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Diffstat (limited to 'drivers/base/power/domain.c')
-rw-r--r-- | drivers/base/power/domain.c | 157 |
1 files changed, 157 insertions, 0 deletions
diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index a3d41a883d83..cd400575ee0e 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -2222,3 +2222,160 @@ int genpd_dev_pm_attach(struct device *dev) } EXPORT_SYMBOL_GPL(genpd_dev_pm_attach); #endif + + +/*** debugfs support ***/ + +#ifdef CONFIG_PM_ADVANCED_DEBUG +#include <linux/pm.h> +#include <linux/device.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/init.h> +#include <linux/kobject.h> +static struct dentry *pm_genpd_debugfs_dir; + +/* + * TODO: This function is a slightly modified version of rtpm_status_show + * from sysfs.c, but dependencies between PM_GENERIC_DOMAINS and PM_RUNTIME + * are too loose to generalize it. + */ +#ifdef CONFIG_PM_RUNTIME +static void rtpm_status_str(struct seq_file *s, struct device *dev) +{ + static const char * const status_lookup[] = { + [RPM_ACTIVE] = "active", + [RPM_RESUMING] = "resuming", + [RPM_SUSPENDED] = "suspended", + [RPM_SUSPENDING] = "suspending" + }; + const char *p = ""; + + if (dev->power.runtime_error) + p = "error"; + else if (dev->power.disable_depth) + p = "unsupported"; + else if (dev->power.runtime_status < ARRAY_SIZE(status_lookup)) + p = status_lookup[dev->power.runtime_status]; + else + WARN_ON(1); + + seq_puts(s, p); +} +#else +static void rtpm_status_str(struct seq_file *s, struct device *dev) +{ + seq_puts(s, "active"); +} +#endif + +static int pm_genpd_summary_one(struct seq_file *s, + struct generic_pm_domain *gpd) +{ + static const char * const status_lookup[] = { + [GPD_STATE_ACTIVE] = "on", + [GPD_STATE_WAIT_MASTER] = "wait-master", + [GPD_STATE_BUSY] = "busy", + [GPD_STATE_REPEAT] = "off-in-progress", + [GPD_STATE_POWER_OFF] = "off" + }; + struct pm_domain_data *pm_data; + const char *kobj_path; + struct gpd_link *link; + int ret; + + ret = mutex_lock_interruptible(&gpd->lock); + if (ret) + return -ERESTARTSYS; + + if (WARN_ON(gpd->status >= ARRAY_SIZE(status_lookup))) + goto exit; + seq_printf(s, "%-30s %-15s ", gpd->name, status_lookup[gpd->status]); + + /* + * Modifications on the list require holding locks on both + * master and slave, so we are safe. + * Also gpd->name is immutable. + */ + list_for_each_entry(link, &gpd->master_links, master_node) { + seq_printf(s, "%s", link->slave->name); + if (!list_is_last(&link->master_node, &gpd->master_links)) + seq_puts(s, ", "); + } + + list_for_each_entry(pm_data, &gpd->dev_list, list_node) { + kobj_path = kobject_get_path(&pm_data->dev->kobj, GFP_KERNEL); + if (kobj_path == NULL) + continue; + + seq_printf(s, "\n %-50s ", kobj_path); + rtpm_status_str(s, pm_data->dev); + kfree(kobj_path); + } + + seq_puts(s, "\n"); +exit: + mutex_unlock(&gpd->lock); + + return 0; +} + +static int pm_genpd_summary_show(struct seq_file *s, void *data) +{ + struct generic_pm_domain *gpd; + int ret = 0; + + seq_puts(s, " domain status slaves\n"); + seq_puts(s, " /device runtime status\n"); + seq_puts(s, "----------------------------------------------------------------------\n"); + + ret = mutex_lock_interruptible(&gpd_list_lock); + if (ret) + return -ERESTARTSYS; + + list_for_each_entry(gpd, &gpd_list, gpd_list_node) { + ret = pm_genpd_summary_one(s, gpd); + if (ret) + break; + } + mutex_unlock(&gpd_list_lock); + + return ret; +} + +static int pm_genpd_summary_open(struct inode *inode, struct file *file) +{ + return single_open(file, pm_genpd_summary_show, NULL); +} + +static const struct file_operations pm_genpd_summary_fops = { + .open = pm_genpd_summary_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int __init pm_genpd_debug_init(void) +{ + struct dentry *d; + + pm_genpd_debugfs_dir = debugfs_create_dir("pm_genpd", NULL); + + if (!pm_genpd_debugfs_dir) + return -ENOMEM; + + d = debugfs_create_file("pm_genpd_summary", S_IRUGO, + pm_genpd_debugfs_dir, NULL, &pm_genpd_summary_fops); + if (!d) + return -ENOMEM; + + return 0; +} +late_initcall(pm_genpd_debug_init); + +static void __exit pm_genpd_debug_exit(void) +{ + debugfs_remove_recursive(pm_genpd_debugfs_dir); +} +__exitcall(pm_genpd_debug_exit); +#endif /* CONFIG_PM_ADVANCED_DEBUG */ |