diff options
author | Jean Pihet <j-pihet@ti.com> | 2011-08-25 09:35:41 -0400 |
---|---|---|
committer | Rafael J. Wysocki <rjw@sisk.pl> | 2011-08-25 09:35:41 -0400 |
commit | 91ff4cb803df6de9114351b9f2f0f39f397ee03e (patch) | |
tree | eb24c7df28d0f93557ac1d9e7d72a79fc3556b97 /drivers/base | |
parent | abe98ec2d86279fe821c9051003a0abc43444f15 (diff) |
PM QoS: Implement per-device PM QoS constraints
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.
A global mutex protects the constraints users from the data being
allocated and free'd.
Signed-off-by: Jean Pihet <j-pihet@ti.com>
Reviewed-by: Kevin Hilman <khilman@ti.com>
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
Diffstat (limited to 'drivers/base')
-rw-r--r-- | drivers/base/power/Makefile | 4 | ||||
-rw-r--r-- | drivers/base/power/main.c | 3 | ||||
-rw-r--r-- | drivers/base/power/qos.c | 338 |
3 files changed, 343 insertions, 2 deletions
diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile index 6488ce12f586..81676dd17900 100644 --- a/drivers/base/power/Makefile +++ b/drivers/base/power/Makefile | |||
@@ -1,4 +1,4 @@ | |||
1 | obj-$(CONFIG_PM) += sysfs.o generic_ops.o common.o | 1 | obj-$(CONFIG_PM) += sysfs.o generic_ops.o common.o qos.o |
2 | obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o | 2 | obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o |
3 | obj-$(CONFIG_PM_RUNTIME) += runtime.o | 3 | obj-$(CONFIG_PM_RUNTIME) += runtime.o |
4 | obj-$(CONFIG_PM_TRACE_RTC) += trace.o | 4 | obj-$(CONFIG_PM_TRACE_RTC) += trace.o |
@@ -6,4 +6,4 @@ obj-$(CONFIG_PM_OPP) += opp.o | |||
6 | obj-$(CONFIG_PM_GENERIC_DOMAINS) += domain.o | 6 | obj-$(CONFIG_PM_GENERIC_DOMAINS) += domain.o |
7 | obj-$(CONFIG_HAVE_CLK) += clock_ops.o | 7 | obj-$(CONFIG_HAVE_CLK) += clock_ops.o |
8 | 8 | ||
9 | ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG \ No newline at end of file | 9 | ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG |
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index a85459126bc6..956443f86254 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c | |||
@@ -22,6 +22,7 @@ | |||
22 | #include <linux/mutex.h> | 22 | #include <linux/mutex.h> |
23 | #include <linux/pm.h> | 23 | #include <linux/pm.h> |
24 | #include <linux/pm_runtime.h> | 24 | #include <linux/pm_runtime.h> |
25 | #include <linux/pm_qos.h> | ||
25 | #include <linux/resume-trace.h> | 26 | #include <linux/resume-trace.h> |
26 | #include <linux/interrupt.h> | 27 | #include <linux/interrupt.h> |
27 | #include <linux/sched.h> | 28 | #include <linux/sched.h> |
@@ -97,6 +98,7 @@ void device_pm_add(struct device *dev) | |||
97 | dev_name(dev->parent)); | 98 | dev_name(dev->parent)); |
98 | list_add_tail(&dev->power.entry, &dpm_list); | 99 | list_add_tail(&dev->power.entry, &dpm_list); |
99 | mutex_unlock(&dpm_list_mtx); | 100 | mutex_unlock(&dpm_list_mtx); |
101 | dev_pm_qos_constraints_init(dev); | ||
100 | } | 102 | } |
101 | 103 | ||
102 | /** | 104 | /** |
@@ -107,6 +109,7 @@ void device_pm_remove(struct device *dev) | |||
107 | { | 109 | { |
108 | pr_debug("PM: Removing info for %s:%s\n", | 110 | pr_debug("PM: Removing info for %s:%s\n", |
109 | dev->bus ? dev->bus->name : "No Bus", dev_name(dev)); | 111 | dev->bus ? dev->bus->name : "No Bus", dev_name(dev)); |
112 | dev_pm_qos_constraints_destroy(dev); | ||
110 | complete_all(&dev->power.completion); | 113 | complete_all(&dev->power.completion); |
111 | mutex_lock(&dpm_list_mtx); | 114 | mutex_lock(&dpm_list_mtx); |
112 | list_del_init(&dev->power.entry); | 115 | 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 000000000000..cc4c541398aa --- /dev/null +++ b/drivers/base/power/qos.c | |||
@@ -0,0 +1,338 @@ | |||
1 | /* | ||
2 | * Devices PM QoS constraints management | ||
3 | * | ||
4 | * Copyright (C) 2011 Texas Instruments, Inc. | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License version 2 as | ||
8 | * published by the Free Software Foundation. | ||
9 | * | ||
10 | * | ||
11 | * This module exposes the interface to kernel space for specifying | ||
12 | * per-device PM QoS dependencies. It provides infrastructure for registration | ||
13 | * of: | ||
14 | * | ||
15 | * Dependents on a QoS value : register requests | ||
16 | * Watchers of QoS value : get notified when target QoS value changes | ||
17 | * | ||
18 | * This QoS design is best effort based. Dependents register their QoS needs. | ||
19 | * Watchers register to keep track of the current QoS needs of the system. | ||
20 | * | ||
21 | * Note about the per-device constraint data struct allocation: | ||
22 | * . The per-device constraints data struct ptr is tored into the device | ||
23 | * dev_pm_info. | ||
24 | * . To minimize the data usage by the per-device constraints, the data struct | ||
25 | * is only allocated at the first call to dev_pm_qos_add_request. | ||
26 | * . The data is later free'd when the device is removed from the system. | ||
27 | * . The constraints_state variable from dev_pm_info tracks the data struct | ||
28 | * allocation state: | ||
29 | * DEV_PM_QOS_NO_DEVICE: No device present or device removed, no data | ||
30 | * allocated, | ||
31 | * DEV_PM_QOS_DEVICE_PRESENT: Device present, data not allocated and will be | ||
32 | * allocated at the first call to dev_pm_qos_add_request, | ||
33 | * DEV_PM_QOS_ALLOCATED: Device present, data allocated. The per-device | ||
34 | * PM QoS constraints framework is operational and constraints can be | ||
35 | * added, updated or removed using the dev_pm_qos_* API. | ||
36 | * . A global mutex protects the constraints users from the data being | ||
37 | * allocated and free'd. | ||
38 | */ | ||
39 | |||
40 | #include <linux/pm_qos.h> | ||
41 | #include <linux/spinlock.h> | ||
42 | #include <linux/slab.h> | ||
43 | #include <linux/device.h> | ||
44 | #include <linux/mutex.h> | ||
45 | |||
46 | |||
47 | static DEFINE_MUTEX(dev_pm_qos_mtx); | ||
48 | |||
49 | /* | ||
50 | * dev_pm_qos_constraints_allocate | ||
51 | * @dev: device to allocate data for | ||
52 | * | ||
53 | * Called at the first call to add_request, for constraint data allocation | ||
54 | * Must be called with the dev_pm_qos_mtx mutex held | ||
55 | */ | ||
56 | static int dev_pm_qos_constraints_allocate(struct device *dev) | ||
57 | { | ||
58 | struct pm_qos_constraints *c; | ||
59 | struct blocking_notifier_head *n; | ||
60 | |||
61 | c = kzalloc(sizeof(*c), GFP_KERNEL); | ||
62 | if (!c) | ||
63 | return -ENOMEM; | ||
64 | |||
65 | n = kzalloc(sizeof(*n), GFP_KERNEL); | ||
66 | if (!n) { | ||
67 | kfree(c); | ||
68 | return -ENOMEM; | ||
69 | } | ||
70 | BLOCKING_INIT_NOTIFIER_HEAD(n); | ||
71 | |||
72 | dev->power.constraints = c; | ||
73 | plist_head_init(&dev->power.constraints->list); | ||
74 | dev->power.constraints->target_value = PM_QOS_DEV_LAT_DEFAULT_VALUE; | ||
75 | dev->power.constraints->default_value = PM_QOS_DEV_LAT_DEFAULT_VALUE; | ||
76 | dev->power.constraints->type = PM_QOS_MIN; | ||
77 | dev->power.constraints->notifiers = n; | ||
78 | dev->power.constraints_state = DEV_PM_QOS_ALLOCATED; | ||
79 | |||
80 | return 0; | ||
81 | } | ||
82 | |||
83 | /** | ||
84 | * dev_pm_qos_constraints_init | ||
85 | * @dev: target device | ||
86 | * | ||
87 | * Called from the device PM subsystem at device insertion | ||
88 | */ | ||
89 | void dev_pm_qos_constraints_init(struct device *dev) | ||
90 | { | ||
91 | mutex_lock(&dev_pm_qos_mtx); | ||
92 | dev->power.constraints_state = DEV_PM_QOS_DEVICE_PRESENT; | ||
93 | mutex_unlock(&dev_pm_qos_mtx); | ||
94 | } | ||
95 | |||
96 | /** | ||
97 | * dev_pm_qos_constraints_destroy | ||
98 | * @dev: target device | ||
99 | * | ||
100 | * Called from the device PM subsystem at device removal | ||
101 | */ | ||
102 | void dev_pm_qos_constraints_destroy(struct device *dev) | ||
103 | { | ||
104 | struct dev_pm_qos_request *req, *tmp; | ||
105 | |||
106 | mutex_lock(&dev_pm_qos_mtx); | ||
107 | |||
108 | if (dev->power.constraints_state == DEV_PM_QOS_ALLOCATED) { | ||
109 | /* Flush the constraints list for the device */ | ||
110 | plist_for_each_entry_safe(req, tmp, | ||
111 | &dev->power.constraints->list, | ||
112 | node) { | ||
113 | /* | ||
114 | * Update constraints list and call the per-device | ||
115 | * callbacks if needed | ||
116 | */ | ||
117 | pm_qos_update_target(req->dev->power.constraints, | ||
118 | &req->node, PM_QOS_REMOVE_REQ, | ||
119 | PM_QOS_DEFAULT_VALUE); | ||
120 | memset(req, 0, sizeof(*req)); | ||
121 | } | ||
122 | |||
123 | kfree(dev->power.constraints->notifiers); | ||
124 | kfree(dev->power.constraints); | ||
125 | dev->power.constraints = NULL; | ||
126 | } | ||
127 | dev->power.constraints_state = DEV_PM_QOS_NO_DEVICE; | ||
128 | |||
129 | mutex_unlock(&dev_pm_qos_mtx); | ||
130 | } | ||
131 | |||
132 | /** | ||
133 | * dev_pm_qos_add_request - inserts new qos request into the list | ||
134 | * @dev: target device for the constraint | ||
135 | * @req: pointer to a preallocated handle | ||
136 | * @value: defines the qos request | ||
137 | * | ||
138 | * This function inserts a new entry in the device constraints list of | ||
139 | * requested qos performance characteristics. It recomputes the aggregate | ||
140 | * QoS expectations of parameters and initializes the dev_pm_qos_request | ||
141 | * handle. Caller needs to save this handle for later use in updates and | ||
142 | * removal. | ||
143 | * | ||
144 | * Returns 1 if the aggregated constraint value has changed, | ||
145 | * 0 if the aggregated constraint value has not changed, | ||
146 | * -EINVAL in case of wrong parameters, -ENODEV if the device has been | ||
147 | * removed from the system | ||
148 | */ | ||
149 | int dev_pm_qos_add_request(struct device *dev, struct dev_pm_qos_request *req, | ||
150 | s32 value) | ||
151 | { | ||
152 | int ret = 0; | ||
153 | |||
154 | if (!dev || !req) /*guard against callers passing in null */ | ||
155 | return -EINVAL; | ||
156 | |||
157 | if (dev_pm_qos_request_active(req)) { | ||
158 | WARN(1, KERN_ERR "dev_pm_qos_add_request() called for already " | ||
159 | "added request\n"); | ||
160 | return -EINVAL; | ||
161 | } | ||
162 | |||
163 | mutex_lock(&dev_pm_qos_mtx); | ||
164 | req->dev = dev; | ||
165 | |||
166 | /* Return if the device has been removed */ | ||
167 | if (req->dev->power.constraints_state == DEV_PM_QOS_NO_DEVICE) { | ||
168 | ret = -ENODEV; | ||
169 | goto out; | ||
170 | } | ||
171 | |||
172 | /* | ||
173 | * Allocate the constraints data on the first call to add_request, | ||
174 | * i.e. only if the data is not already allocated and if the device has | ||
175 | * not been removed | ||
176 | */ | ||
177 | if (dev->power.constraints_state == DEV_PM_QOS_DEVICE_PRESENT) | ||
178 | ret = dev_pm_qos_constraints_allocate(dev); | ||
179 | |||
180 | if (!ret) | ||
181 | ret = pm_qos_update_target(dev->power.constraints, &req->node, | ||
182 | PM_QOS_ADD_REQ, value); | ||
183 | |||
184 | out: | ||
185 | mutex_unlock(&dev_pm_qos_mtx); | ||
186 | return ret; | ||
187 | } | ||
188 | EXPORT_SYMBOL_GPL(dev_pm_qos_add_request); | ||
189 | |||
190 | /** | ||
191 | * dev_pm_qos_update_request - modifies an existing qos request | ||
192 | * @req : handle to list element holding a dev_pm_qos request to use | ||
193 | * @new_value: defines the qos request | ||
194 | * | ||
195 | * Updates an existing dev PM qos request along with updating the | ||
196 | * target value. | ||
197 | * | ||
198 | * Attempts are made to make this code callable on hot code paths. | ||
199 | * | ||
200 | * Returns 1 if the aggregated constraint value has changed, | ||
201 | * 0 if the aggregated constraint value has not changed, | ||
202 | * -EINVAL in case of wrong parameters, -ENODEV if the device has been | ||
203 | * removed from the system | ||
204 | */ | ||
205 | int dev_pm_qos_update_request(struct dev_pm_qos_request *req, | ||
206 | s32 new_value) | ||
207 | { | ||
208 | int ret = 0; | ||
209 | |||
210 | if (!req) /*guard against callers passing in null */ | ||
211 | return -EINVAL; | ||
212 | |||
213 | if (!dev_pm_qos_request_active(req)) { | ||
214 | WARN(1, KERN_ERR "dev_pm_qos_update_request() called for " | ||
215 | "unknown object\n"); | ||
216 | return -EINVAL; | ||
217 | } | ||
218 | |||
219 | mutex_lock(&dev_pm_qos_mtx); | ||
220 | |||
221 | if (req->dev->power.constraints_state == DEV_PM_QOS_ALLOCATED) { | ||
222 | if (new_value != req->node.prio) | ||
223 | ret = pm_qos_update_target(req->dev->power.constraints, | ||
224 | &req->node, | ||
225 | PM_QOS_UPDATE_REQ, | ||
226 | new_value); | ||
227 | } else { | ||
228 | /* Return if the device has been removed */ | ||
229 | ret = -ENODEV; | ||
230 | } | ||
231 | |||
232 | mutex_unlock(&dev_pm_qos_mtx); | ||
233 | return ret; | ||
234 | } | ||
235 | EXPORT_SYMBOL_GPL(dev_pm_qos_update_request); | ||
236 | |||
237 | /** | ||
238 | * dev_pm_qos_remove_request - modifies an existing qos request | ||
239 | * @req: handle to request list element | ||
240 | * | ||
241 | * Will remove pm qos request from the list of constraints and | ||
242 | * recompute the current target value. Call this on slow code paths. | ||
243 | * | ||
244 | * Returns 1 if the aggregated constraint value has changed, | ||
245 | * 0 if the aggregated constraint value has not changed, | ||
246 | * -EINVAL in case of wrong parameters, -ENODEV if the device has been | ||
247 | * removed from the system | ||
248 | */ | ||
249 | int dev_pm_qos_remove_request(struct dev_pm_qos_request *req) | ||
250 | { | ||
251 | int ret = 0; | ||
252 | |||
253 | if (!req) /*guard against callers passing in null */ | ||
254 | return -EINVAL; | ||
255 | |||
256 | if (!dev_pm_qos_request_active(req)) { | ||
257 | WARN(1, KERN_ERR "dev_pm_qos_remove_request() called for " | ||
258 | "unknown object\n"); | ||
259 | return -EINVAL; | ||
260 | } | ||
261 | |||
262 | mutex_lock(&dev_pm_qos_mtx); | ||
263 | |||
264 | if (req->dev->power.constraints_state == DEV_PM_QOS_ALLOCATED) { | ||
265 | ret = pm_qos_update_target(req->dev->power.constraints, | ||
266 | &req->node, PM_QOS_REMOVE_REQ, | ||
267 | PM_QOS_DEFAULT_VALUE); | ||
268 | memset(req, 0, sizeof(*req)); | ||
269 | } else { | ||
270 | /* Return if the device has been removed */ | ||
271 | ret = -ENODEV; | ||
272 | } | ||
273 | |||
274 | mutex_unlock(&dev_pm_qos_mtx); | ||
275 | return ret; | ||
276 | } | ||
277 | EXPORT_SYMBOL_GPL(dev_pm_qos_remove_request); | ||
278 | |||
279 | /** | ||
280 | * dev_pm_qos_add_notifier - sets notification entry for changes to target value | ||
281 | * of per-device PM QoS constraints | ||
282 | * | ||
283 | * @dev: target device for the constraint | ||
284 | * @notifier: notifier block managed by caller. | ||
285 | * | ||
286 | * Will register the notifier into a notification chain that gets called | ||
287 | * upon changes to the target value for the device. | ||
288 | */ | ||
289 | int dev_pm_qos_add_notifier(struct device *dev, struct notifier_block *notifier) | ||
290 | { | ||
291 | int retval = 0; | ||
292 | |||
293 | mutex_lock(&dev_pm_qos_mtx); | ||
294 | |||
295 | /* Silently return if the device has been removed */ | ||
296 | if (dev->power.constraints_state != DEV_PM_QOS_ALLOCATED) | ||
297 | goto out; | ||
298 | |||
299 | retval = blocking_notifier_chain_register( | ||
300 | dev->power.constraints->notifiers, | ||
301 | notifier); | ||
302 | |||
303 | out: | ||
304 | mutex_unlock(&dev_pm_qos_mtx); | ||
305 | return retval; | ||
306 | } | ||
307 | EXPORT_SYMBOL_GPL(dev_pm_qos_add_notifier); | ||
308 | |||
309 | /** | ||
310 | * dev_pm_qos_remove_notifier - deletes notification for changes to target value | ||
311 | * of per-device PM QoS constraints | ||
312 | * | ||
313 | * @dev: target device for the constraint | ||
314 | * @notifier: notifier block to be removed. | ||
315 | * | ||
316 | * Will remove the notifier from the notification chain that gets called | ||
317 | * upon changes to the target value. | ||
318 | */ | ||
319 | int dev_pm_qos_remove_notifier(struct device *dev, | ||
320 | struct notifier_block *notifier) | ||
321 | { | ||
322 | int retval = 0; | ||
323 | |||
324 | mutex_lock(&dev_pm_qos_mtx); | ||
325 | |||
326 | /* Silently return if the device has been removed */ | ||
327 | if (dev->power.constraints_state != DEV_PM_QOS_ALLOCATED) | ||
328 | goto out; | ||
329 | |||
330 | retval = blocking_notifier_chain_unregister( | ||
331 | dev->power.constraints->notifiers, | ||
332 | notifier); | ||
333 | |||
334 | out: | ||
335 | mutex_unlock(&dev_pm_qos_mtx); | ||
336 | return retval; | ||
337 | } | ||
338 | EXPORT_SYMBOL_GPL(dev_pm_qos_remove_notifier); | ||