From ef27bed1870dbd5fd363ff5ec51eebd5a695e277 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 25 Aug 2011 15:34:01 +0200 Subject: PM: Reference counting of power.subsys_data Since the power.subsys_data device field will be used by multiple filesystems, introduce a reference counting mechanism for it to avoid freeing it prematurely or changing its value at a wrong time. Make the PM clocks management code that currently is the only user of power.subsys_data use the new reference counting. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/Makefile | 2 +- drivers/base/power/clock_ops.c | 24 +++--------- drivers/base/power/common.c | 87 ++++++++++++++++++++++++++++++++++++++++++ include/linux/pm.h | 3 ++ 4 files changed, 96 insertions(+), 20 deletions(-) create mode 100644 drivers/base/power/common.c diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile index 2639ae79a37..6488ce12f58 100644 --- a/drivers/base/power/Makefile +++ b/drivers/base/power/Makefile @@ -1,4 +1,4 @@ -obj-$(CONFIG_PM) += sysfs.o generic_ops.o +obj-$(CONFIG_PM) += sysfs.o generic_ops.o common.o obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o obj-$(CONFIG_PM_RUNTIME) += runtime.o obj-$(CONFIG_PM_TRACE_RTC) += trace.o diff --git a/drivers/base/power/clock_ops.c b/drivers/base/power/clock_ops.c index b7f1db4f594..8383e2488d7 100644 --- a/drivers/base/power/clock_ops.c +++ b/drivers/base/power/clock_ops.c @@ -140,12 +140,8 @@ void pm_clk_remove(struct device *dev, const char *con_id) void pm_clk_init(struct device *dev) { struct pm_subsys_data *psd = dev_to_psd(dev); - - if (!psd) - return; - - INIT_LIST_HEAD(&psd->clock_list); - spin_lock_init(&psd->lock); + if (psd) + INIT_LIST_HEAD(&psd->clock_list); } /** @@ -157,16 +153,8 @@ void pm_clk_init(struct device *dev) */ int pm_clk_create(struct device *dev) { - struct pm_subsys_data *psd; - - psd = kzalloc(sizeof(*psd), GFP_KERNEL); - if (!psd) { - dev_err(dev, "Not enough memory for PM clock data.\n"); - return -ENOMEM; - } - dev->power.subsys_data = psd; - pm_clk_init(dev); - return 0; + int ret = dev_pm_get_subsys_data(dev); + return ret < 0 ? ret : 0; } /** @@ -185,8 +173,6 @@ void pm_clk_destroy(struct device *dev) if (!psd) return; - dev->power.subsys_data = NULL; - spin_lock_irq(&psd->lock); list_for_each_entry_safe_reverse(ce, c, &psd->clock_list, node) @@ -194,7 +180,7 @@ void pm_clk_destroy(struct device *dev) spin_unlock_irq(&psd->lock); - kfree(psd); + dev_pm_put_subsys_data(dev); } #endif /* CONFIG_PM */ diff --git a/drivers/base/power/common.c b/drivers/base/power/common.c new file mode 100644 index 00000000000..d398cf029b3 --- /dev/null +++ b/drivers/base/power/common.c @@ -0,0 +1,87 @@ +/* + * drivers/base/power/common.c - Common device power management code. + * + * Copyright (C) 2011 Rafael J. Wysocki , Renesas Electronics Corp. + * + * This file is released under the GPLv2. + */ + +#include +#include +#include +#include +#include +#include + +/** + * dev_pm_get_subsys_data - Create or refcount power.subsys_data for device. + * @dev: Device to handle. + * + * If power.subsys_data is NULL, point it to a new object, otherwise increment + * its reference counter. Return 1 if a new object has been created, otherwise + * return 0 or error code. + */ +int dev_pm_get_subsys_data(struct device *dev) +{ + struct pm_subsys_data *psd; + int ret = 0; + + psd = kzalloc(sizeof(*psd), GFP_KERNEL); + if (!psd) + return -ENOMEM; + + spin_lock_irq(&dev->power.lock); + + if (dev->power.subsys_data) { + dev->power.subsys_data->refcount++; + } else { + spin_lock_init(&psd->lock); + psd->refcount = 1; + dev->power.subsys_data = psd; + pm_clk_init(dev); + psd = NULL; + ret = 1; + } + + spin_unlock_irq(&dev->power.lock); + + /* kfree() verifies that its argument is nonzero. */ + kfree(psd); + + return ret; +} +EXPORT_SYMBOL_GPL(dev_pm_get_subsys_data); + +/** + * dev_pm_put_subsys_data - Drop reference to power.subsys_data. + * @dev: Device to handle. + * + * If the reference counter of power.subsys_data is zero after dropping the + * reference, power.subsys_data is removed. Return 1 if that happens or 0 + * otherwise. + */ +int dev_pm_put_subsys_data(struct device *dev) +{ + struct pm_subsys_data *psd; + int ret = 0; + + spin_lock_irq(&dev->power.lock); + + psd = dev_to_psd(dev); + if (!psd) { + ret = -EINVAL; + goto out; + } + + if (--psd->refcount == 0) { + dev->power.subsys_data = NULL; + kfree(psd); + ret = 1; + } + + out: + spin_unlock_irq(&dev->power.lock); + + return ret; +} +EXPORT_SYMBOL_GPL(dev_pm_put_subsys_data); diff --git a/include/linux/pm.h b/include/linux/pm.h index bf5ee37388d..c6b5a0a41ab 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h @@ -423,6 +423,7 @@ struct wakeup_source; struct pm_subsys_data { spinlock_t lock; + unsigned int refcount; #ifdef CONFIG_PM_CLK struct list_head clock_list; #endif @@ -473,6 +474,8 @@ struct dev_pm_info { }; extern void update_pm_runtime_accounting(struct device *dev); +extern int dev_pm_get_subsys_data(struct device *dev); +extern int dev_pm_put_subsys_data(struct device *dev); /* * Power domains provide callbacks that are executed during system suspend, -- cgit v1.2.3-70-g09d2