Message ID | 1313075212-8366-7-git-send-email-j-pihet@ti.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On Thu, Aug 11, 2011 at 05:06:43PM +0200, jean.pihet@newoldbits.com wrote: > From: Jean Pihet <j-pihet@ti.com> > > Implement the per-device PM QoS constraints by creating a device > PM QoS API, which calls the PM QoS constraints management core code. > > The per-device latency constraints data strctures are stored > in the device dev_pm_info struct. > > The device PM code calls the init and destroy of the per-device constraints > data struct in order to support the dynamic insertion and removal of the > devices in the system. > > To minimize the data usage by the per-device constraints, the data struct > is only allocated at the first call to dev_pm_qos_add_request. > The data is later free'd when the device is removed from the system. > > Signed-off-by: Jean Pihet <j-pihet@ti.com> > --- > drivers/base/power/Makefile | 4 +- > drivers/base/power/main.c | 3 + > drivers/base/power/qos.c | 262 +++++++++++++++++++++++++++++++++++++++++++ > include/linux/pm.h | 9 ++ > include/linux/pm_qos.h | 39 +++++++ > 5 files changed, 315 insertions(+), 2 deletions(-) > create mode 100644 drivers/base/power/qos.c > > diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile > index 3647e11..1a61f89 100644 > --- a/drivers/base/power/Makefile > +++ b/drivers/base/power/Makefile > @@ -1,8 +1,8 @@ > -obj-$(CONFIG_PM) += sysfs.o generic_ops.o > +obj-$(CONFIG_PM) += sysfs.o generic_ops.o qos.o > obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o > obj-$(CONFIG_PM_RUNTIME) += runtime.o > obj-$(CONFIG_PM_TRACE_RTC) += trace.o > obj-$(CONFIG_PM_OPP) += opp.o > obj-$(CONFIG_HAVE_CLK) += clock_ops.o > > -ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG > \ No newline at end of file > +ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG > diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c > index 06f09bf..f5c0e0e 100644 > --- a/drivers/base/power/main.c > +++ b/drivers/base/power/main.c > @@ -22,6 +22,7 @@ > #include <linux/mutex.h> > #include <linux/pm.h> > #include <linux/pm_runtime.h> > +#include <linux/pm_qos.h> > #include <linux/resume-trace.h> > #include <linux/interrupt.h> > #include <linux/sched.h> > @@ -97,6 +98,7 @@ void device_pm_add(struct device *dev) > dev_name(dev->parent)); > list_add_tail(&dev->power.entry, &dpm_list); > mutex_unlock(&dpm_list_mtx); > + dev_pm_qos_constraints_init(dev); > } > > /** > @@ -107,6 +109,7 @@ void device_pm_remove(struct device *dev) > { > pr_debug("PM: Removing info for %s:%s\n", > dev->bus ? dev->bus->name : "No Bus", dev_name(dev)); > + dev_pm_qos_constraints_destroy(dev); > complete_all(&dev->power.completion); > mutex_lock(&dpm_list_mtx); > list_del_init(&dev->power.entry); > diff --git a/drivers/base/power/qos.c b/drivers/base/power/qos.c > new file mode 100644 > index 0000000..465e419 > --- /dev/null > +++ b/drivers/base/power/qos.c > @@ -0,0 +1,262 @@ > +/* > + * This module exposes the interface to kernel space for specifying > + * per-device PM QoS dependencies. It provides infrastructure for registration > + * of: > + * > + * Dependents on a QoS value : register requests > + * Watchers of QoS value : get notified when target QoS value changes > + * > + * This QoS design is best effort based. Dependents register their QoS needs. > + * Watchers register to keep track of the current QoS needs of the system. > + * > + * Note about the per-device constraint data struct allocation: > + * . The per-device constraints data struct ptr is tored into the device > + * dev_pm_info. > + * . To minimize the data usage by the per-device constraints, the data struct > + * is only allocated at the first call to dev_pm_qos_add_request. > + * . The data is later free'd when the device is removed from the system. > + * . The constraints_state variable from dev_pm_info tracks the data struct > + * allocation state: > + * DEV_PM_QOS_NO_DEVICE: No device present or device removed, no data > + * allocated, > + * DEV_PM_QOS_DEVICE_PRESENT: Device present, data not allocated and will be > + * allocated at the first call to dev_pm_qos_add_request, > + * DEV_PM_QOS_ALLOCATED: Device present, data allocated. The per-device > + * PM QoS constraints framework is operational and constraints can be > + * added, updated or removed using the dev_pm_qos_* API. > + */ > + > +/*#define DEBUG*/ > + > +#include <linux/pm_qos.h> > +#include <linux/sched.h> > +#include <linux/spinlock.h> > +#include <linux/slab.h> > +#include <linux/time.h> > +#include <linux/fs.h> > +#include <linux/device.h> > +#include <linux/string.h> > +#include <linux/platform_device.h> > +#include <linux/init.h> > +#include <linux/kernel.h> > + > + > +static void dev_pm_qos_constraints_allocate(struct device *dev); > + > +int dev_pm_qos_request_active(struct dev_pm_qos_request *req) > +{ > + return req->dev != 0; > +} > +EXPORT_SYMBOL_GPL(dev_pm_qos_request_active); > + > +/** > + * dev_pm_qos_add_request - inserts new qos request into the list > + * @req: pointer to a preallocated handle > + * @dev: target device for the constraint > + * @value: defines the qos request > + * > + * This function inserts a new entry in the device constraints list of > + * requested qos performance characteristics. It recomputes the aggregate > + * QoS expectations of parameters and initializes the dev_pm_qos_request > + * handle. Caller needs to save this handle for later use in updates and > + * removal. > + */ > +void dev_pm_qos_add_request(struct dev_pm_qos_request *req, struct device *dev, > + s32 value) > +{ > + if (!req) /*guard against callers passing in null */ > + return; > + > + if (dev_pm_qos_request_active(req)) { > + WARN(1, KERN_ERR "dev_pm_qos_add_request() called for already " > + "added request\n"); > + return; > + } > + req->dev = dev; > + > + /* Allocate the constraints struct on the first call to add_request */ > + if (req->dev->power.constraints_state == DEV_PM_QOS_DEVICE_PRESENT) > + dev_pm_qos_constraints_allocate(dev); > + > + /* Silently return if the device has been removed */ > + if (req->dev->power.constraints_state != DEV_PM_QOS_ALLOCATED) > + return; > + > + pm_qos_update_target(dev->power.constraints, > + &req->node, PM_QOS_ADD_REQ, value); > +} > +EXPORT_SYMBOL_GPL(dev_pm_qos_add_request); > + > +/** > + * dev_pm_qos_update_request - modifies an existing qos request > + * @req : handle to list element holding a dev_pm_qos request to use > + * @value: defines the qos request > + * > + * Updates an existing dev PM qos request along with updating the > + * target value. > + * > + * Attempts are made to make this code callable on hot code paths. > + */ > +void dev_pm_qos_update_request(struct dev_pm_qos_request *req, > + s32 new_value) > +{ > + if (!req) /*guard against callers passing in null */ > + return; > + > + if (!dev_pm_qos_request_active(req)) { > + WARN(1, KERN_ERR "dev_pm_qos_update_request() called for " > + "unknown object\n"); > + return; > + } > + > + /* Silently return if the device has been removed */ > + if (req->dev->power.constraints_state != DEV_PM_QOS_ALLOCATED) > + return; > + > + if (new_value != req->node.prio) > + pm_qos_update_target( > + req->dev->power.constraints, > + &req->node, PM_QOS_UPDATE_REQ, new_value); > +} > +EXPORT_SYMBOL_GPL(dev_pm_qos_update_request); > + > +/** > + * dev_pm_qos_remove_request - modifies an existing qos request > + * @req: handle to request list element > + * > + * Will remove pm qos request from the list of constraints and > + * recompute the current target value. Call this on slow code paths. > + */ > +void dev_pm_qos_remove_request(struct dev_pm_qos_request *req) > +{ > + if (!req) /*guard against callers passing in null */ > + return; > + > + if (!dev_pm_qos_request_active(req)) { > + WARN(1, KERN_ERR "dev_pm_qos_remove_request() called for " > + "unknown object\n"); > + return; > + } > + > + /* Silently return if the device has been removed */ > + if (req->dev->power.constraints_state != DEV_PM_QOS_ALLOCATED) > + return; > + > + pm_qos_update_target(req->dev->power.constraints, > + &req->node, PM_QOS_REMOVE_REQ, > + PM_QOS_DEFAULT_VALUE); > + memset(req, 0, sizeof(*req)); > +} > +EXPORT_SYMBOL_GPL(dev_pm_qos_remove_request); > + > +/** > + * dev_pm_qos_add_notifier - sets notification entry for changes to target value > + * of per-device PM QoS constraints > + * > + * @dev: target device for the constraint > + * @notifier: notifier block managed by caller. > + * > + * Will register the notifier into a notification chain that gets called > + * upon changes to the target value for the device. > + */ > +int dev_pm_qos_add_notifier(struct device *dev, struct notifier_block *notifier) > +{ > + int retval = 0; > + > + /* Silently return if the device has been removed */ > + if (dev->power.constraints_state != DEV_PM_QOS_ALLOCATED) > + return retval; > + > + retval = blocking_notifier_chain_register( > + dev->power.constraints->notifiers, > + notifier); > + > + return retval; > +} > +EXPORT_SYMBOL_GPL(dev_pm_qos_add_notifier); > + > +/** > + * dev_pm_qos_remove_notifier - deletes notification for changes to target value > + * of per-device PM QoS constraints > + * > + * @dev: target device for the constraint > + * @notifier: notifier block to be removed. > + * > + * Will remove the notifier from the notification chain that gets called > + * upon changes to the target value. > + */ > +int dev_pm_qos_remove_notifier(struct device *dev, > + struct notifier_block *notifier) > +{ > + int retval = 0; > + > + /* Silently return if the device has been removed */ > + if (dev->power.constraints_state != DEV_PM_QOS_ALLOCATED) > + return retval; > + > + retval = blocking_notifier_chain_unregister( > + dev->power.constraints->notifiers, > + notifier); > + > + return retval; > +} > +EXPORT_SYMBOL_GPL(dev_pm_qos_remove_notifier); > + > +/* Called at the first call to add_request, for constraint data allocation */ > +static void dev_pm_qos_constraints_allocate(struct device *dev) > +{ > + struct pm_qos_constraints *c; > + struct blocking_notifier_head *n; > + > + c = kzalloc(sizeof(*c), GFP_KERNEL); > + if (!c) > + return; > + > + n = kzalloc(sizeof(*n), GFP_KERNEL); > + if (!n) { > + kfree(c); > + return; > + } > + BLOCKING_INIT_NOTIFIER_HEAD(n); > + > + dev->power.constraints = c; > + plist_head_init(&dev->power.constraints->list, &dev->power.lock); > + dev->power.constraints->target_value = PM_QOS_DEV_LAT_DEFAULT_VALUE; > + dev->power.constraints->default_value = PM_QOS_DEV_LAT_DEFAULT_VALUE; > + dev->power.constraints->type = PM_QOS_MIN; > + dev->power.constraints->notifiers = n; > + dev->power.constraints_state = DEV_PM_QOS_ALLOCATED; > +} > + > +/* Called from the device PM subsystem at device insertion */ > +void dev_pm_qos_constraints_init(struct device *dev) > +{ > + dev->power.constraints_state = DEV_PM_QOS_DEVICE_PRESENT; > +} > + > +/* Called from the device PM subsystem at device removal */ > +void dev_pm_qos_constraints_destroy(struct device *dev) > +{ > + struct dev_pm_qos_request *req, *tmp; > + enum dev_pm_qos_state constraints_state = dev->power.constraints_state; > + > + dev->power.constraints_state = DEV_PM_QOS_NO_DEVICE; > + if (constraints_state == DEV_PM_QOS_ALLOCATED) { > + /* Flush the constraints list for the device */ > + plist_for_each_entry_safe(req, tmp, > + &dev->power.constraints->list, > + node) > + /* > + * Update constraints list and call the per-device > + * callbacks if needed > + */ > + pm_qos_update_target(req->dev->power.constraints, > + &req->node, PM_QOS_REMOVE_REQ, > + PM_QOS_DEFAULT_VALUE); > + > + kfree(dev->power.constraints->notifiers); > + kfree(dev->power.constraints); > + dev->power.constraints = NULL; > + } > +} > + > diff --git a/include/linux/pm.h b/include/linux/pm.h > index 411e4f4..aa6dc53 100644 > --- a/include/linux/pm.h > +++ b/include/linux/pm.h > @@ -421,6 +421,13 @@ enum rpm_request { > > struct wakeup_source; > > +/* Per-device PM QoS constraints data struct state */ > +enum dev_pm_qos_state { > + DEV_PM_QOS_NO_DEVICE, /* No device present */ > + DEV_PM_QOS_DEVICE_PRESENT, /* Device present, data not allocated */ > + DEV_PM_QOS_ALLOCATED, /* Device present, data allocated */ > +}; > + > struct dev_pm_info { > pm_message_t power_state; > unsigned int can_wakeup:1; > @@ -463,6 +470,8 @@ struct dev_pm_info { > unsigned long accounting_timestamp; > void *subsys_data; /* Owned by the subsystem. */ > #endif > + struct pm_qos_constraints *constraints; > + enum dev_pm_qos_state constraints_state; > }; > > extern void update_pm_runtime_accounting(struct device *dev); > diff --git a/include/linux/pm_qos.h b/include/linux/pm_qos.h > index 84aa150..178eaa1 100644 > --- a/include/linux/pm_qos.h > +++ b/include/linux/pm_qos.h > @@ -19,12 +19,18 @@ > #define PM_QOS_CPU_DMA_LAT_DEFAULT_VALUE (2000 * USEC_PER_SEC) > #define PM_QOS_NETWORK_LAT_DEFAULT_VALUE (2000 * USEC_PER_SEC) > #define PM_QOS_NETWORK_THROUGHPUT_DEFAULT_VALUE 0 > +#define PM_QOS_DEV_LAT_DEFAULT_VALUE 0 > > struct pm_qos_request { > struct plist_node node; > int pm_qos_class; > }; > > +struct dev_pm_qos_request { > + struct plist_node node; > + struct device *dev; > +}; > + > enum pm_qos_type { > PM_QOS_UNITIALIZED, > PM_QOS_MAX, /* return the largest value */ > @@ -64,6 +70,18 @@ int pm_qos_request(int pm_qos_class); > int pm_qos_add_notifier(int pm_qos_class, struct notifier_block *notifier); > int pm_qos_remove_notifier(int pm_qos_class, struct notifier_block *notifier); > int pm_qos_request_active(struct pm_qos_request *req); > + > +int dev_pm_qos_request_active(struct dev_pm_qos_request *req); > +void dev_pm_qos_add_request(struct dev_pm_qos_request *req, struct device *dev, > + s32 value); > +void dev_pm_qos_update_request(struct dev_pm_qos_request *req, s32 new_value); > +void dev_pm_qos_remove_request(struct dev_pm_qos_request *req); > +int dev_pm_qos_add_notifier(struct device *dev, > + struct notifier_block *notifier); > +int dev_pm_qos_remove_notifier(struct device *dev, > + struct notifier_block *notifier); > +void dev_pm_qos_constraints_init(struct device *dev); > +void dev_pm_qos_constraints_destroy(struct device *dev); > #else > static inline int pm_qos_update_target(struct pm_qos_constraints *c, > struct plist_node *node, > @@ -89,6 +107,27 @@ static inline int pm_qos_remove_notifier(int pm_qos_class, > { return 0; } > static inline int pm_qos_request_active(struct pm_qos_request *req) > { return 0; } > + > +static inline int dev_pm_qos_request_active(struct dev_pm_qos_request *req) > + { return 0; } > +static inline void dev_pm_qos_add_request(struct dev_pm_qos_request *req, > + struct device *dev, s32 value) > + { return; } > +static inline void dev_pm_qos_update_request(struct dev_pm_qos_request *req, > + s32 new_value) > + { return; } > +static inline void dev_pm_qos_remove_request(struct dev_pm_qos_request *req) > + { return; } > +static inline int dev_pm_qos_add_notifier(struct device *dev, > + struct notifier_block *notifier) > + { return 0; } > +static inline int dev_pm_qos_remove_notifier(struct device *dev, > + struct notifier_block *notifier) > + { return 0; } > +static inline void dev_pm_qos_constraints_init(struct device *dev) > + { return; } > +static inline void dev_pm_qos_constraints_destroy(struct device *dev) > + { return; } > #endif > > #endif > -- > 1.7.2.5 > looks good (except for the use of those enums I don't care for) --mark -- To unsubscribe from this list: send the line "unsubscribe linux-omap" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Hi, Well, it looks like I should have reviewed this one more carefully. On Thursday, August 11, 2011, jean.pihet@newoldbits.com wrote: > From: Jean Pihet <j-pihet@ti.com> > > Implement the per-device PM QoS constraints by creating a device > PM QoS API, which calls the PM QoS constraints management core code. > > The per-device latency constraints data strctures are stored > in the device dev_pm_info struct. > > The device PM code calls the init and destroy of the per-device constraints > data struct in order to support the dynamic insertion and removal of the > devices in the system. > > To minimize the data usage by the per-device constraints, the data struct > is only allocated at the first call to dev_pm_qos_add_request. > The data is later free'd when the device is removed from the system. > > Signed-off-by: Jean Pihet <j-pihet@ti.com> > --- > drivers/base/power/Makefile | 4 +- > drivers/base/power/main.c | 3 + > drivers/base/power/qos.c | 262 +++++++++++++++++++++++++++++++++++++++++++ > include/linux/pm.h | 9 ++ > include/linux/pm_qos.h | 39 +++++++ > 5 files changed, 315 insertions(+), 2 deletions(-) > create mode 100644 drivers/base/power/qos.c > > diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile > index 3647e11..1a61f89 100644 > --- a/drivers/base/power/Makefile > +++ b/drivers/base/power/Makefile > @@ -1,8 +1,8 @@ > -obj-$(CONFIG_PM) += sysfs.o generic_ops.o > +obj-$(CONFIG_PM) += sysfs.o generic_ops.o qos.o > obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o > obj-$(CONFIG_PM_RUNTIME) += runtime.o > obj-$(CONFIG_PM_TRACE_RTC) += trace.o > obj-$(CONFIG_PM_OPP) += opp.o > obj-$(CONFIG_HAVE_CLK) += clock_ops.o > > -ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG > \ No newline at end of file > +ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG > diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c > index 06f09bf..f5c0e0e 100644 > --- a/drivers/base/power/main.c > +++ b/drivers/base/power/main.c > @@ -22,6 +22,7 @@ > #include <linux/mutex.h> > #include <linux/pm.h> > #include <linux/pm_runtime.h> > +#include <linux/pm_qos.h> > #include <linux/resume-trace.h> > #include <linux/interrupt.h> > #include <linux/sched.h> > @@ -97,6 +98,7 @@ void device_pm_add(struct device *dev) > dev_name(dev->parent)); > list_add_tail(&dev->power.entry, &dpm_list); > mutex_unlock(&dpm_list_mtx); > + dev_pm_qos_constraints_init(dev); > } > > /** > @@ -107,6 +109,7 @@ void device_pm_remove(struct device *dev) > { > pr_debug("PM: Removing info for %s:%s\n", > dev->bus ? dev->bus->name : "No Bus", dev_name(dev)); > + dev_pm_qos_constraints_destroy(dev); > complete_all(&dev->power.completion); > mutex_lock(&dpm_list_mtx); > list_del_init(&dev->power.entry); > diff --git a/drivers/base/power/qos.c b/drivers/base/power/qos.c > new file mode 100644 > index 0000000..465e419 > --- /dev/null > +++ b/drivers/base/power/qos.c > @@ -0,0 +1,262 @@ > +/* > + * This module exposes the interface to kernel space for specifying > + * per-device PM QoS dependencies. It provides infrastructure for registration > + * of: > + * > + * Dependents on a QoS value : register requests > + * Watchers of QoS value : get notified when target QoS value changes > + * > + * This QoS design is best effort based. Dependents register their QoS needs. > + * Watchers register to keep track of the current QoS needs of the system. > + * > + * Note about the per-device constraint data struct allocation: > + * . The per-device constraints data struct ptr is tored into the device > + * dev_pm_info. > + * . To minimize the data usage by the per-device constraints, the data struct > + * is only allocated at the first call to dev_pm_qos_add_request. > + * . The data is later free'd when the device is removed from the system. > + * . The constraints_state variable from dev_pm_info tracks the data struct > + * allocation state: > + * DEV_PM_QOS_NO_DEVICE: No device present or device removed, no data > + * allocated, > + * DEV_PM_QOS_DEVICE_PRESENT: Device present, data not allocated and will be > + * allocated at the first call to dev_pm_qos_add_request, > + * DEV_PM_QOS_ALLOCATED: Device present, data allocated. The per-device > + * PM QoS constraints framework is operational and constraints can be > + * added, updated or removed using the dev_pm_qos_* API. > + */ > + > +/*#define DEBUG*/ Please remove this. > + > +#include <linux/pm_qos.h> > +#include <linux/sched.h> > +#include <linux/spinlock.h> > +#include <linux/slab.h> > +#include <linux/time.h> > +#include <linux/fs.h> > +#include <linux/device.h> > +#include <linux/string.h> > +#include <linux/platform_device.h> > +#include <linux/init.h> > +#include <linux/kernel.h> Are you sure all of the headers are necessary? > + > + > +static void dev_pm_qos_constraints_allocate(struct device *dev); > + > +int dev_pm_qos_request_active(struct dev_pm_qos_request *req) > +{ > + return req->dev != 0; > +} > +EXPORT_SYMBOL_GPL(dev_pm_qos_request_active); That should be a static inline in a header. > + > +/** > + * dev_pm_qos_add_request - inserts new qos request into the list > + * @req: pointer to a preallocated handle > + * @dev: target device for the constraint > + * @value: defines the qos request > + * > + * This function inserts a new entry in the device constraints list of > + * requested qos performance characteristics. It recomputes the aggregate > + * QoS expectations of parameters and initializes the dev_pm_qos_request > + * handle. Caller needs to save this handle for later use in updates and > + * removal. > + */ > +void dev_pm_qos_add_request(struct dev_pm_qos_request *req, struct device *dev, > + s32 value) I'd use a different ordering of arguments, namely (dev, req, value). > +{ > + if (!req) /*guard against callers passing in null */ > + return; Why not to check for !dev too? > + > + if (dev_pm_qos_request_active(req)) { > + WARN(1, KERN_ERR "dev_pm_qos_add_request() called for already " > + "added request\n"); > + return; > + } > + req->dev = dev; > + > + /* Allocate the constraints struct on the first call to add_request */ > + if (req->dev->power.constraints_state == DEV_PM_QOS_DEVICE_PRESENT) > + dev_pm_qos_constraints_allocate(dev); Why not to do + if (!req->dev->power.constraints) + dev_pm_qos_constraints_allocate(dev); > + > + /* Silently return if the device has been removed */ > + if (req->dev->power.constraints_state != DEV_PM_QOS_ALLOCATED) > + return; > + Hmm. What will happen if two callers run dev_pm_qos_add_request() concurrently for the same device? > + pm_qos_update_target(dev->power.constraints, > + &req->node, PM_QOS_ADD_REQ, value); > +} > +EXPORT_SYMBOL_GPL(dev_pm_qos_add_request); > + > +/** > + * dev_pm_qos_update_request - modifies an existing qos request > + * @req : handle to list element holding a dev_pm_qos request to use > + * @value: defines the qos request > + * > + * Updates an existing dev PM qos request along with updating the > + * target value. > + * > + * Attempts are made to make this code callable on hot code paths. > + */ > +void dev_pm_qos_update_request(struct dev_pm_qos_request *req, > + s32 new_value) > +{ > + if (!req) /*guard against callers passing in null */ > + return; > + > + if (!dev_pm_qos_request_active(req)) { > + WARN(1, KERN_ERR "dev_pm_qos_update_request() called for " > + "unknown object\n"); > + return; > + } > + > + /* Silently return if the device has been removed */ > + if (req->dev->power.constraints_state != DEV_PM_QOS_ALLOCATED) > + return; > + > + if (new_value != req->node.prio) > + pm_qos_update_target( > + req->dev->power.constraints, > + &req->node, PM_QOS_UPDATE_REQ, new_value); I'm not sure what prevents dev_pm_qos_constraints_destroy() from removing the list from under us while we're executing this? > +} > +EXPORT_SYMBOL_GPL(dev_pm_qos_update_request); > + > +/** > + * dev_pm_qos_remove_request - modifies an existing qos request > + * @req: handle to request list element > + * > + * Will remove pm qos request from the list of constraints and > + * recompute the current target value. Call this on slow code paths. > + */ > +void dev_pm_qos_remove_request(struct dev_pm_qos_request *req) > +{ > + if (!req) /*guard against callers passing in null */ > + return; > + > + if (!dev_pm_qos_request_active(req)) { > + WARN(1, KERN_ERR "dev_pm_qos_remove_request() called for " > + "unknown object\n"); > + return; > + } > + > + /* Silently return if the device has been removed */ > + if (req->dev->power.constraints_state != DEV_PM_QOS_ALLOCATED) > + return; > + > + pm_qos_update_target(req->dev->power.constraints, > + &req->node, PM_QOS_REMOVE_REQ, > + PM_QOS_DEFAULT_VALUE); Same here. > + memset(req, 0, sizeof(*req)); > +} > +EXPORT_SYMBOL_GPL(dev_pm_qos_remove_request); > + > +/** > + * dev_pm_qos_add_notifier - sets notification entry for changes to target value > + * of per-device PM QoS constraints > + * > + * @dev: target device for the constraint > + * @notifier: notifier block managed by caller. > + * > + * Will register the notifier into a notification chain that gets called > + * upon changes to the target value for the device. > + */ > +int dev_pm_qos_add_notifier(struct device *dev, struct notifier_block *notifier) > +{ > + int retval = 0; > + > + /* Silently return if the device has been removed */ > + if (dev->power.constraints_state != DEV_PM_QOS_ALLOCATED) > + return retval; > + > + retval = blocking_notifier_chain_register( > + dev->power.constraints->notifiers, > + notifier); That may be racing with dev_pm_qos_constraints_destroy() too. > + > + return retval; > +} > +EXPORT_SYMBOL_GPL(dev_pm_qos_add_notifier); > + > +/** > + * dev_pm_qos_remove_notifier - deletes notification for changes to target value > + * of per-device PM QoS constraints > + * > + * @dev: target device for the constraint > + * @notifier: notifier block to be removed. > + * > + * Will remove the notifier from the notification chain that gets called > + * upon changes to the target value. > + */ > +int dev_pm_qos_remove_notifier(struct device *dev, > + struct notifier_block *notifier) > +{ > + int retval = 0; > + > + /* Silently return if the device has been removed */ > + if (dev->power.constraints_state != DEV_PM_QOS_ALLOCATED) > + return retval; > + > + retval = blocking_notifier_chain_unregister( > + dev->power.constraints->notifiers, > + notifier); Same here. > + > + return retval; > +} > +EXPORT_SYMBOL_GPL(dev_pm_qos_remove_notifier); > + > +/* Called at the first call to add_request, for constraint data allocation */ > +static void dev_pm_qos_constraints_allocate(struct device *dev) > +{ > + struct pm_qos_constraints *c; > + struct blocking_notifier_head *n; > + > + c = kzalloc(sizeof(*c), GFP_KERNEL); > + if (!c) > + return; > + > + n = kzalloc(sizeof(*n), GFP_KERNEL); > + if (!n) { > + kfree(c); > + return; > + } > + BLOCKING_INIT_NOTIFIER_HEAD(n); > + > + dev->power.constraints = c; > + plist_head_init(&dev->power.constraints->list, &dev->power.lock); > + dev->power.constraints->target_value = PM_QOS_DEV_LAT_DEFAULT_VALUE; > + dev->power.constraints->default_value = PM_QOS_DEV_LAT_DEFAULT_VALUE; > + dev->power.constraints->type = PM_QOS_MIN; > + dev->power.constraints->notifiers = n; > + dev->power.constraints_state = DEV_PM_QOS_ALLOCATED; > +} > + > +/* Called from the device PM subsystem at device insertion */ > +void dev_pm_qos_constraints_init(struct device *dev) > +{ > + dev->power.constraints_state = DEV_PM_QOS_DEVICE_PRESENT; Hmm. Is it insufficient to check if "constraints" is not NULL? > +} > + > +/* Called from the device PM subsystem at device removal */ > +void dev_pm_qos_constraints_destroy(struct device *dev) > +{ > + struct dev_pm_qos_request *req, *tmp; > + enum dev_pm_qos_state constraints_state = dev->power.constraints_state; > + > + dev->power.constraints_state = DEV_PM_QOS_NO_DEVICE; I'm not sure what the purpose of this is. I'd simply check if "constraints" is not NULL and I'd flush the list if not. Moreover, that has to go under a lock so that it doesn't race with the other functions above. I think you can reuse power.lock for that. > + if (constraints_state == DEV_PM_QOS_ALLOCATED) { > + /* Flush the constraints list for the device */ > + plist_for_each_entry_safe(req, tmp, > + &dev->power.constraints->list, > + node) > + /* > + * Update constraints list and call the per-device > + * callbacks if needed > + */ > + pm_qos_update_target(req->dev->power.constraints, > + &req->node, PM_QOS_REMOVE_REQ, > + PM_QOS_DEFAULT_VALUE); > + > + kfree(dev->power.constraints->notifiers); > + kfree(dev->power.constraints); > + dev->power.constraints = NULL; > + } > +} > + Thanks, Rafael -- To unsubscribe from this list: send the line "unsubscribe linux-omap" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Rafael, 2011/8/13 Rafael J. Wysocki <rjw@sisk.pl>: > Hi, > > Well, it looks like I should have reviewed this one more carefully. > > On Thursday, August 11, 2011, jean.pihet@newoldbits.com wrote: >> From: Jean Pihet <j-pihet@ti.com> >> >> Implement the per-device PM QoS constraints by creating a device >> PM QoS API, which calls the PM QoS constraints management core code. >> >> The per-device latency constraints data strctures are stored >> in the device dev_pm_info struct. >> >> The device PM code calls the init and destroy of the per-device constraints >> data struct in order to support the dynamic insertion and removal of the >> devices in the system. >> >> To minimize the data usage by the per-device constraints, the data struct >> is only allocated at the first call to dev_pm_qos_add_request. >> The data is later free'd when the device is removed from the system. >> >> Signed-off-by: Jean Pihet <j-pihet@ti.com> >> --- ... >> +/*#define DEBUG*/ > > Please remove this. Ok > >> + >> +#include <linux/pm_qos.h> >> +#include <linux/sched.h> >> +#include <linux/spinlock.h> >> +#include <linux/slab.h> >> +#include <linux/time.h> >> +#include <linux/fs.h> >> +#include <linux/device.h> >> +#include <linux/string.h> >> +#include <linux/platform_device.h> >> +#include <linux/init.h> >> +#include <linux/kernel.h> > > Are you sure all of the headers are necessary? No. I can check that. > >> + >> + >> +static void dev_pm_qos_constraints_allocate(struct device *dev); >> + >> +int dev_pm_qos_request_active(struct dev_pm_qos_request *req) >> +{ >> + return req->dev != 0; >> +} >> +EXPORT_SYMBOL_GPL(dev_pm_qos_request_active); > > That should be a static inline in a header. Ok > >> + >> +/** >> + * dev_pm_qos_add_request - inserts new qos request into the list >> + * @req: pointer to a preallocated handle >> + * @dev: target device for the constraint >> + * @value: defines the qos request >> + * >> + * This function inserts a new entry in the device constraints list of >> + * requested qos performance characteristics. It recomputes the aggregate >> + * QoS expectations of parameters and initializes the dev_pm_qos_request >> + * handle. Caller needs to save this handle for later use in updates and >> + * removal. >> + */ >> +void dev_pm_qos_add_request(struct dev_pm_qos_request *req, struct device *dev, >> + s32 value) > > I'd use a different ordering of arguments, namely (dev, req, value). Ok > >> +{ >> + if (!req) /*guard against callers passing in null */ >> + return; > > Why not to check for !dev too? Ok that makes sense > >> + >> + if (dev_pm_qos_request_active(req)) { >> + WARN(1, KERN_ERR "dev_pm_qos_add_request() called for already " >> + "added request\n"); >> + return; >> + } >> + req->dev = dev; >> + >> + /* Allocate the constraints struct on the first call to add_request */ >> + if (req->dev->power.constraints_state == DEV_PM_QOS_DEVICE_PRESENT) >> + dev_pm_qos_constraints_allocate(dev); > > Why not to do > > + if (!req->dev->power.constraints) > + dev_pm_qos_constraints_allocate(dev); Cf. my comments at the end of this message for the data structs alloc/free and the locking. > >> + >> + /* Silently return if the device has been removed */ >> + if (req->dev->power.constraints_state != DEV_PM_QOS_ALLOCATED) >> + return; >> + > > Hmm. What will happen if two callers run dev_pm_qos_add_request() > concurrently for the same device? update_target is using the power.lock to protect the constraints lists changes. > >> + pm_qos_update_target(dev->power.constraints, >> + &req->node, PM_QOS_ADD_REQ, value); >> +} >> +EXPORT_SYMBOL_GPL(dev_pm_qos_add_request); >> + >> +/** >> + * dev_pm_qos_update_request - modifies an existing qos request >> + * @req : handle to list element holding a dev_pm_qos request to use >> + * @value: defines the qos request >> + * >> + * Updates an existing dev PM qos request along with updating the >> + * target value. >> + * >> + * Attempts are made to make this code callable on hot code paths. >> + */ >> +void dev_pm_qos_update_request(struct dev_pm_qos_request *req, >> + s32 new_value) >> +{ >> + if (!req) /*guard against callers passing in null */ >> + return; >> + >> + if (!dev_pm_qos_request_active(req)) { >> + WARN(1, KERN_ERR "dev_pm_qos_update_request() called for " >> + "unknown object\n"); >> + return; >> + } >> + >> + /* Silently return if the device has been removed */ >> + if (req->dev->power.constraints_state != DEV_PM_QOS_ALLOCATED) >> + return; >> + >> + if (new_value != req->node.prio) >> + pm_qos_update_target( >> + req->dev->power.constraints, >> + &req->node, PM_QOS_UPDATE_REQ, new_value); > > I'm not sure what prevents dev_pm_qos_constraints_destroy() from removing > the list from under us while we're executing this? Cf. my comments at the end of this message for the data structs alloc/free and the locking. > >> +} >> +EXPORT_SYMBOL_GPL(dev_pm_qos_update_request); >> + >> +/** >> + * dev_pm_qos_remove_request - modifies an existing qos request >> + * @req: handle to request list element >> + * >> + * Will remove pm qos request from the list of constraints and >> + * recompute the current target value. Call this on slow code paths. >> + */ >> +void dev_pm_qos_remove_request(struct dev_pm_qos_request *req) >> +{ >> + if (!req) /*guard against callers passing in null */ >> + return; >> + >> + if (!dev_pm_qos_request_active(req)) { >> + WARN(1, KERN_ERR "dev_pm_qos_remove_request() called for " >> + "unknown object\n"); >> + return; >> + } >> + >> + /* Silently return if the device has been removed */ >> + if (req->dev->power.constraints_state != DEV_PM_QOS_ALLOCATED) >> + return; >> + >> + pm_qos_update_target(req->dev->power.constraints, >> + &req->node, PM_QOS_REMOVE_REQ, >> + PM_QOS_DEFAULT_VALUE); > > Same here. > >> + memset(req, 0, sizeof(*req)); >> +} >> +EXPORT_SYMBOL_GPL(dev_pm_qos_remove_request); >> + >> +/** >> + * dev_pm_qos_add_notifier - sets notification entry for changes to target value >> + * of per-device PM QoS constraints >> + * >> + * @dev: target device for the constraint >> + * @notifier: notifier block managed by caller. >> + * >> + * Will register the notifier into a notification chain that gets called >> + * upon changes to the target value for the device. >> + */ >> +int dev_pm_qos_add_notifier(struct device *dev, struct notifier_block *notifier) >> +{ >> + int retval = 0; >> + >> + /* Silently return if the device has been removed */ >> + if (dev->power.constraints_state != DEV_PM_QOS_ALLOCATED) >> + return retval; >> + >> + retval = blocking_notifier_chain_register( >> + dev->power.constraints->notifiers, >> + notifier); > > That may be racing with dev_pm_qos_constraints_destroy() too. > >> + >> + return retval; >> +} >> +EXPORT_SYMBOL_GPL(dev_pm_qos_add_notifier); >> + >> +/** >> + * dev_pm_qos_remove_notifier - deletes notification for changes to target value >> + * of per-device PM QoS constraints >> + * >> + * @dev: target device for the constraint >> + * @notifier: notifier block to be removed. >> + * >> + * Will remove the notifier from the notification chain that gets called >> + * upon changes to the target value. >> + */ >> +int dev_pm_qos_remove_notifier(struct device *dev, >> + struct notifier_block *notifier) >> +{ >> + int retval = 0; >> + >> + /* Silently return if the device has been removed */ >> + if (dev->power.constraints_state != DEV_PM_QOS_ALLOCATED) >> + return retval; >> + >> + retval = blocking_notifier_chain_unregister( >> + dev->power.constraints->notifiers, >> + notifier); > > Same here. > >> + >> + return retval; >> +} >> +EXPORT_SYMBOL_GPL(dev_pm_qos_remove_notifier); >> + >> +/* Called at the first call to add_request, for constraint data allocation */ >> +static void dev_pm_qos_constraints_allocate(struct device *dev) >> +{ >> + struct pm_qos_constraints *c; >> + struct blocking_notifier_head *n; >> + >> + c = kzalloc(sizeof(*c), GFP_KERNEL); >> + if (!c) >> + return; >> + >> + n = kzalloc(sizeof(*n), GFP_KERNEL); >> + if (!n) { >> + kfree(c); >> + return; >> + } >> + BLOCKING_INIT_NOTIFIER_HEAD(n); >> + >> + dev->power.constraints = c; >> + plist_head_init(&dev->power.constraints->list, &dev->power.lock); >> + dev->power.constraints->target_value = PM_QOS_DEV_LAT_DEFAULT_VALUE; >> + dev->power.constraints->default_value = PM_QOS_DEV_LAT_DEFAULT_VALUE; >> + dev->power.constraints->type = PM_QOS_MIN; >> + dev->power.constraints->notifiers = n; >> + dev->power.constraints_state = DEV_PM_QOS_ALLOCATED; >> +} >> + >> +/* Called from the device PM subsystem at device insertion */ >> +void dev_pm_qos_constraints_init(struct device *dev) >> +{ >> + dev->power.constraints_state = DEV_PM_QOS_DEVICE_PRESENT; > > Hmm. Is it insufficient to check if "constraints" is not NULL? > >> +} >> + >> +/* Called from the device PM subsystem at device removal */ >> +void dev_pm_qos_constraints_destroy(struct device *dev) >> +{ >> + struct dev_pm_qos_request *req, *tmp; >> + enum dev_pm_qos_state constraints_state = dev->power.constraints_state; >> + >> + dev->power.constraints_state = DEV_PM_QOS_NO_DEVICE; > > I'm not sure what the purpose of this is. I'd simply check if "constraints" > is not NULL and I'd flush the list if not. > > Moreover, that has to go under a lock so that it doesn't race with > the other functions above. I think you can reuse power.lock for that. > Your remarks are inline with the concerns I had about the adata structs alloc/free and the locking. 1) data structs alloc/free As described in the changelog: >> To minimize the data usage by the per-device constraints, the data struct >> is only allocated at the first call to dev_pm_qos_add_request. >> The data is later free'd when the device is removed from the system. A basic state machine is needed in order to allocate the data at the first call to add_request and free it when the device is removed. Another option is to allocate the data when the device is added to the system and free it when the device is removed. That would make the code simpler but it is using a lot of memory for unneeded data. 2) Race conditions I will add a lock to isolate the API callers from dev_pm_qos_constraints_destroy(). The power.lock is already used by update_target so another lock is needed to protect the data allocation/free. I will rework this tomorrow and re-submit asap when it is ready. Is that OK? > > Thanks, > Rafael > Thanks, Jean -- To unsubscribe from this list: send the line "unsubscribe linux-omap" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Hi, On Sunday, August 14, 2011, Jean Pihet wrote: ... > >> + > >> + if (dev_pm_qos_request_active(req)) { > >> + WARN(1, KERN_ERR "dev_pm_qos_add_request() called for already " > >> + "added request\n"); > >> + return; > >> + } > >> + req->dev = dev; > >> + > >> + /* Allocate the constraints struct on the first call to add_request */ > >> + if (req->dev->power.constraints_state == DEV_PM_QOS_DEVICE_PRESENT) > >> + dev_pm_qos_constraints_allocate(dev); > > > > Why not to do > > > > + if (!req->dev->power.constraints) > > + dev_pm_qos_constraints_allocate(dev); > > Cf. my comments at the end of this message for the data structs > alloc/free and the locking. > > > > >> + > >> + /* Silently return if the device has been removed */ > >> + if (req->dev->power.constraints_state != DEV_PM_QOS_ALLOCATED) > >> + return; > >> + > > > > Hmm. What will happen if two callers run dev_pm_qos_add_request() > > concurrently for the same device? > update_target is using the power.lock to protect the constraints lists changes. I was referring to the scenario in particular: Suppose that there are no constraits for dev initially. A -> calls dev_pm_qos_add_request(dev) B -> calls dev_pm_qos_add_request(dev) A -> sees power.constraints_state == DEV_PM_QOS_DEVICE_PRESENT and calls dev_pm_qos_constraints_allocate(dev) B -> sees power.constraints_state == DEV_PM_QOS_DEVICE_PRESENT and calls dev_pm_qos_constraints_allocate(dev) As a result, the structure allocated by A is leaked. ... > > Your remarks are inline with the concerns I had about the adata > structs alloc/free and the locking. > > 1) data structs alloc/free > As described in the changelog: > >> To minimize the data usage by the per-device constraints, the data struct > >> is only allocated at the first call to dev_pm_qos_add_request. > >> The data is later free'd when the device is removed from the system. > A basic state machine is needed in order to allocate the data at the > first call to add_request and free it when the device is removed. > Another option is to allocate the data when the device is added to the > system and free it when the device is removed. That would make the > code simpler but it is using a lot of memory for unneeded data. That's fine. You simply need to be more careful about the possible race conditions when the constraints objects are created and destroyed. > 2) Race conditions > I will add a lock to isolate the API callers from > dev_pm_qos_constraints_destroy(). > The power.lock is already used by update_target so another lock is > needed to protect the data allocation/free. OK > I will rework this tomorrow and re-submit asap when it is ready. > Is that OK? Yes, it is. Thanks, Rafael -- To unsubscribe from this list: send the line "unsubscribe linux-omap" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile index 3647e11..1a61f89 100644 --- a/drivers/base/power/Makefile +++ b/drivers/base/power/Makefile @@ -1,8 +1,8 @@ -obj-$(CONFIG_PM) += sysfs.o generic_ops.o +obj-$(CONFIG_PM) += sysfs.o generic_ops.o qos.o obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o obj-$(CONFIG_PM_RUNTIME) += runtime.o obj-$(CONFIG_PM_TRACE_RTC) += trace.o obj-$(CONFIG_PM_OPP) += opp.o obj-$(CONFIG_HAVE_CLK) += clock_ops.o -ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG \ No newline at end of file +ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 06f09bf..f5c0e0e 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -22,6 +22,7 @@ #include <linux/mutex.h> #include <linux/pm.h> #include <linux/pm_runtime.h> +#include <linux/pm_qos.h> #include <linux/resume-trace.h> #include <linux/interrupt.h> #include <linux/sched.h> @@ -97,6 +98,7 @@ void device_pm_add(struct device *dev) dev_name(dev->parent)); list_add_tail(&dev->power.entry, &dpm_list); mutex_unlock(&dpm_list_mtx); + dev_pm_qos_constraints_init(dev); } /** @@ -107,6 +109,7 @@ void device_pm_remove(struct device *dev) { pr_debug("PM: Removing info for %s:%s\n", dev->bus ? dev->bus->name : "No Bus", dev_name(dev)); + dev_pm_qos_constraints_destroy(dev); complete_all(&dev->power.completion); mutex_lock(&dpm_list_mtx); list_del_init(&dev->power.entry); diff --git a/drivers/base/power/qos.c b/drivers/base/power/qos.c new file mode 100644 index 0000000..465e419 --- /dev/null +++ b/drivers/base/power/qos.c @@ -0,0 +1,262 @@ +/* + * This module exposes the interface to kernel space for specifying + * per-device PM QoS dependencies. It provides infrastructure for registration + * of: + * + * Dependents on a QoS value : register requests + * Watchers of QoS value : get notified when target QoS value changes + * + * This QoS design is best effort based. Dependents register their QoS needs. + * Watchers register to keep track of the current QoS needs of the system. + * + * Note about the per-device constraint data struct allocation: + * . The per-device constraints data struct ptr is tored into the device + * dev_pm_info. + * . To minimize the data usage by the per-device constraints, the data struct + * is only allocated at the first call to dev_pm_qos_add_request. + * . The data is later free'd when the device is removed from the system. + * . The constraints_state variable from dev_pm_info tracks the data struct + * allocation state: + * DEV_PM_QOS_NO_DEVICE: No device present or device removed, no data + * allocated, + * DEV_PM_QOS_DEVICE_PRESENT: Device present, data not allocated and will be + * allocated at the first call to dev_pm_qos_add_request, + * DEV_PM_QOS_ALLOCATED: Device present, data allocated. The per-device + * PM QoS constraints framework is operational and constraints can be + * added, updated or removed using the dev_pm_qos_* API. + */ + +/*#define DEBUG*/ + +#include <linux/pm_qos.h> +#include <linux/sched.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/fs.h> +#include <linux/device.h> +#include <linux/string.h> +#include <linux/platform_device.h> +#include <linux/init.h> +#include <linux/kernel.h> + + +static void dev_pm_qos_constraints_allocate(struct device *dev); + +int dev_pm_qos_request_active(struct dev_pm_qos_request *req) +{ + return req->dev != 0; +} +EXPORT_SYMBOL_GPL(dev_pm_qos_request_active); + +/** + * dev_pm_qos_add_request - inserts new qos request into the list + * @req: pointer to a preallocated handle + * @dev: target device for the constraint + * @value: defines the qos request + * + * This function inserts a new entry in the device constraints list of + * requested qos performance characteristics. It recomputes the aggregate + * QoS expectations of parameters and initializes the dev_pm_qos_request + * handle. Caller needs to save this handle for later use in updates and + * removal. + */ +void dev_pm_qos_add_request(struct dev_pm_qos_request *req, struct device *dev, + s32 value) +{ + if (!req) /*guard against callers passing in null */ + return; + + if (dev_pm_qos_request_active(req)) { + WARN(1, KERN_ERR "dev_pm_qos_add_request() called for already " + "added request\n"); + return; + } + req->dev = dev; + + /* Allocate the constraints struct on the first call to add_request */ + if (req->dev->power.constraints_state == DEV_PM_QOS_DEVICE_PRESENT) + dev_pm_qos_constraints_allocate(dev); + + /* Silently return if the device has been removed */ + if (req->dev->power.constraints_state != DEV_PM_QOS_ALLOCATED) + return; + + pm_qos_update_target(dev->power.constraints, + &req->node, PM_QOS_ADD_REQ, value); +} +EXPORT_SYMBOL_GPL(dev_pm_qos_add_request); + +/** + * dev_pm_qos_update_request - modifies an existing qos request + * @req : handle to list element holding a dev_pm_qos request to use + * @value: defines the qos request + * + * Updates an existing dev PM qos request along with updating the + * target value. + * + * Attempts are made to make this code callable on hot code paths. + */ +void dev_pm_qos_update_request(struct dev_pm_qos_request *req, + s32 new_value) +{ + if (!req) /*guard against callers passing in null */ + return; + + if (!dev_pm_qos_request_active(req)) { + WARN(1, KERN_ERR "dev_pm_qos_update_request() called for " + "unknown object\n"); + return; + } + + /* Silently return if the device has been removed */ + if (req->dev->power.constraints_state != DEV_PM_QOS_ALLOCATED) + return; + + if (new_value != req->node.prio) + pm_qos_update_target( + req->dev->power.constraints, + &req->node, PM_QOS_UPDATE_REQ, new_value); +} +EXPORT_SYMBOL_GPL(dev_pm_qos_update_request); + +/** + * dev_pm_qos_remove_request - modifies an existing qos request + * @req: handle to request list element + * + * Will remove pm qos request from the list of constraints and + * recompute the current target value. Call this on slow code paths. + */ +void dev_pm_qos_remove_request(struct dev_pm_qos_request *req) +{ + if (!req) /*guard against callers passing in null */ + return; + + if (!dev_pm_qos_request_active(req)) { + WARN(1, KERN_ERR "dev_pm_qos_remove_request() called for " + "unknown object\n"); + return; + } + + /* Silently return if the device has been removed */ + if (req->dev->power.constraints_state != DEV_PM_QOS_ALLOCATED) + return; + + pm_qos_update_target(req->dev->power.constraints, + &req->node, PM_QOS_REMOVE_REQ, + PM_QOS_DEFAULT_VALUE); + memset(req, 0, sizeof(*req)); +} +EXPORT_SYMBOL_GPL(dev_pm_qos_remove_request); + +/** + * dev_pm_qos_add_notifier - sets notification entry for changes to target value + * of per-device PM QoS constraints + * + * @dev: target device for the constraint + * @notifier: notifier block managed by caller. + * + * Will register the notifier into a notification chain that gets called + * upon changes to the target value for the device. + */ +int dev_pm_qos_add_notifier(struct device *dev, struct notifier_block *notifier) +{ + int retval = 0; + + /* Silently return if the device has been removed */ + if (dev->power.constraints_state != DEV_PM_QOS_ALLOCATED) + return retval; + + retval = blocking_notifier_chain_register( + dev->power.constraints->notifiers, + notifier); + + return retval; +} +EXPORT_SYMBOL_GPL(dev_pm_qos_add_notifier); + +/** + * dev_pm_qos_remove_notifier - deletes notification for changes to target value + * of per-device PM QoS constraints + * + * @dev: target device for the constraint + * @notifier: notifier block to be removed. + * + * Will remove the notifier from the notification chain that gets called + * upon changes to the target value. + */ +int dev_pm_qos_remove_notifier(struct device *dev, + struct notifier_block *notifier) +{ + int retval = 0; + + /* Silently return if the device has been removed */ + if (dev->power.constraints_state != DEV_PM_QOS_ALLOCATED) + return retval; + + retval = blocking_notifier_chain_unregister( + dev->power.constraints->notifiers, + notifier); + + return retval; +} +EXPORT_SYMBOL_GPL(dev_pm_qos_remove_notifier); + +/* Called at the first call to add_request, for constraint data allocation */ +static void dev_pm_qos_constraints_allocate(struct device *dev) +{ + struct pm_qos_constraints *c; + struct blocking_notifier_head *n; + + c = kzalloc(sizeof(*c), GFP_KERNEL); + if (!c) + return; + + n = kzalloc(sizeof(*n), GFP_KERNEL); + if (!n) { + kfree(c); + return; + } + BLOCKING_INIT_NOTIFIER_HEAD(n); + + dev->power.constraints = c; + plist_head_init(&dev->power.constraints->list, &dev->power.lock); + dev->power.constraints->target_value = PM_QOS_DEV_LAT_DEFAULT_VALUE; + dev->power.constraints->default_value = PM_QOS_DEV_LAT_DEFAULT_VALUE; + dev->power.constraints->type = PM_QOS_MIN; + dev->power.constraints->notifiers = n; + dev->power.constraints_state = DEV_PM_QOS_ALLOCATED; +} + +/* Called from the device PM subsystem at device insertion */ +void dev_pm_qos_constraints_init(struct device *dev) +{ + dev->power.constraints_state = DEV_PM_QOS_DEVICE_PRESENT; +} + +/* Called from the device PM subsystem at device removal */ +void dev_pm_qos_constraints_destroy(struct device *dev) +{ + struct dev_pm_qos_request *req, *tmp; + enum dev_pm_qos_state constraints_state = dev->power.constraints_state; + + dev->power.constraints_state = DEV_PM_QOS_NO_DEVICE; + if (constraints_state == DEV_PM_QOS_ALLOCATED) { + /* Flush the constraints list for the device */ + plist_for_each_entry_safe(req, tmp, + &dev->power.constraints->list, + node) + /* + * Update constraints list and call the per-device + * callbacks if needed + */ + pm_qos_update_target(req->dev->power.constraints, + &req->node, PM_QOS_REMOVE_REQ, + PM_QOS_DEFAULT_VALUE); + + kfree(dev->power.constraints->notifiers); + kfree(dev->power.constraints); + dev->power.constraints = NULL; + } +} + diff --git a/include/linux/pm.h b/include/linux/pm.h index 411e4f4..aa6dc53 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h @@ -421,6 +421,13 @@ enum rpm_request { struct wakeup_source; +/* Per-device PM QoS constraints data struct state */ +enum dev_pm_qos_state { + DEV_PM_QOS_NO_DEVICE, /* No device present */ + DEV_PM_QOS_DEVICE_PRESENT, /* Device present, data not allocated */ + DEV_PM_QOS_ALLOCATED, /* Device present, data allocated */ +}; + struct dev_pm_info { pm_message_t power_state; unsigned int can_wakeup:1; @@ -463,6 +470,8 @@ struct dev_pm_info { unsigned long accounting_timestamp; void *subsys_data; /* Owned by the subsystem. */ #endif + struct pm_qos_constraints *constraints; + enum dev_pm_qos_state constraints_state; }; extern void update_pm_runtime_accounting(struct device *dev); diff --git a/include/linux/pm_qos.h b/include/linux/pm_qos.h index 84aa150..178eaa1 100644 --- a/include/linux/pm_qos.h +++ b/include/linux/pm_qos.h @@ -19,12 +19,18 @@ #define PM_QOS_CPU_DMA_LAT_DEFAULT_VALUE (2000 * USEC_PER_SEC) #define PM_QOS_NETWORK_LAT_DEFAULT_VALUE (2000 * USEC_PER_SEC) #define PM_QOS_NETWORK_THROUGHPUT_DEFAULT_VALUE 0 +#define PM_QOS_DEV_LAT_DEFAULT_VALUE 0 struct pm_qos_request { struct plist_node node; int pm_qos_class; }; +struct dev_pm_qos_request { + struct plist_node node; + struct device *dev; +}; + enum pm_qos_type { PM_QOS_UNITIALIZED, PM_QOS_MAX, /* return the largest value */ @@ -64,6 +70,18 @@ int pm_qos_request(int pm_qos_class); int pm_qos_add_notifier(int pm_qos_class, struct notifier_block *notifier); int pm_qos_remove_notifier(int pm_qos_class, struct notifier_block *notifier); int pm_qos_request_active(struct pm_qos_request *req); + +int dev_pm_qos_request_active(struct dev_pm_qos_request *req); +void dev_pm_qos_add_request(struct dev_pm_qos_request *req, struct device *dev, + s32 value); +void dev_pm_qos_update_request(struct dev_pm_qos_request *req, s32 new_value); +void dev_pm_qos_remove_request(struct dev_pm_qos_request *req); +int dev_pm_qos_add_notifier(struct device *dev, + struct notifier_block *notifier); +int dev_pm_qos_remove_notifier(struct device *dev, + struct notifier_block *notifier); +void dev_pm_qos_constraints_init(struct device *dev); +void dev_pm_qos_constraints_destroy(struct device *dev); #else static inline int pm_qos_update_target(struct pm_qos_constraints *c, struct plist_node *node, @@ -89,6 +107,27 @@ static inline int pm_qos_remove_notifier(int pm_qos_class, { return 0; } static inline int pm_qos_request_active(struct pm_qos_request *req) { return 0; } + +static inline int dev_pm_qos_request_active(struct dev_pm_qos_request *req) + { return 0; } +static inline void dev_pm_qos_add_request(struct dev_pm_qos_request *req, + struct device *dev, s32 value) + { return; } +static inline void dev_pm_qos_update_request(struct dev_pm_qos_request *req, + s32 new_value) + { return; } +static inline void dev_pm_qos_remove_request(struct dev_pm_qos_request *req) + { return; } +static inline int dev_pm_qos_add_notifier(struct device *dev, + struct notifier_block *notifier) + { return 0; } +static inline int dev_pm_qos_remove_notifier(struct device *dev, + struct notifier_block *notifier) + { return 0; } +static inline void dev_pm_qos_constraints_init(struct device *dev) + { return; } +static inline void dev_pm_qos_constraints_destroy(struct device *dev) + { return; } #endif #endif