aboutsummaryrefslogtreecommitdiffstats
path: root/kernel/pm_qos_params.c
diff options
context:
space:
mode:
authorMark Gross <mgross@linux.intel.com>2008-02-05 01:30:08 -0500
committerLinus Torvalds <torvalds@woody.linux-foundation.org>2008-02-05 12:44:22 -0500
commitd82b35186eaa816267f044bd70cc0acb3c7971a3 (patch)
treee8de56c122fd8040086e974895afbb8299045c0f /kernel/pm_qos_params.c
parent4ef7229ffa93695e346d510b871452811509ea65 (diff)
pm qos infrastructure and interface
The following patch is a generalization of the latency.c implementation done by Arjan last year. It provides infrastructure for more than one parameter, and exposes a user mode interface for processes to register pm_qos expectations of processes. This interface provides a kernel and user mode interface for registering performance expectations by drivers, subsystems and user space applications on one of the parameters. Currently we have {cpu_dma_latency, network_latency, network_throughput} as the initial set of pm_qos parameters. The infrastructure exposes multiple misc device nodes one per implemented parameter. The set of parameters implement is defined by pm_qos_power_init() and pm_qos_params.h. This is done because having the available parameters being runtime configurable or changeable from a driver was seen as too easy to abuse. For each parameter a list of performance requirements is maintained along with an aggregated target value. The aggregated target value is updated with changes to the requirement list or elements of the list. Typically the aggregated target value is simply the max or min of the requirement values held in the parameter list elements. >From kernel mode the use of this interface is simple: pm_qos_add_requirement(param_id, name, target_value): Will insert a named element in the list for that identified PM_QOS parameter with the target value. Upon change to this list the new target is recomputed and any registered notifiers are called only if the target value is now different. pm_qos_update_requirement(param_id, name, new_target_value): Will search the list identified by the param_id for the named list element and then update its target value, calling the notification tree if the aggregated target is changed. with that name is already registered. pm_qos_remove_requirement(param_id, name): Will search the identified list for the named element and remove it, after removal it will update the aggregate target and call the notification tree if the target was changed as a result of removing the named requirement. >From user mode: Only processes can register a pm_qos requirement. To provide for automatic cleanup for process the interface requires the process to register its parameter requirements in the following way: To register the default pm_qos target for the specific parameter, the process must open one of /dev/[cpu_dma_latency, network_latency, network_throughput] As long as the device node is held open that process has a registered requirement on the parameter. The name of the requirement is "process_<PID>" derived from the current->pid from within the open system call. To change the requested target value the process needs to write a s32 value to the open device node. This translates to a pm_qos_update_requirement call. To remove the user mode request for a target value simply close the device node. [akpm@linux-foundation.org: fix warnings] [akpm@linux-foundation.org: fix build] [akpm@linux-foundation.org: fix build again] [akpm@linux-foundation.org: coding-style fixes] Signed-off-by: mark gross <mgross@linux.intel.com> Cc: "John W. Linville" <linville@tuxdriver.com> Cc: Len Brown <lenb@kernel.org> Cc: Jaroslav Kysela <perex@suse.cz> Cc: Takashi Iwai <tiwai@suse.de> Cc: Arjan van de Ven <arjan@infradead.org> Cc: Venki Pallipadi <venkatesh.pallipadi@intel.com> Cc: Adam Belay <abelay@novell.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'kernel/pm_qos_params.c')
-rw-r--r--kernel/pm_qos_params.c425
1 files changed, 425 insertions, 0 deletions
diff --git a/kernel/pm_qos_params.c b/kernel/pm_qos_params.c
new file mode 100644
index 000000000000..0afe32be4c85
--- /dev/null
+++ b/kernel/pm_qos_params.c
@@ -0,0 +1,425 @@
1/*
2 * This module exposes the interface to kernel space for specifying
3 * QoS dependencies. It provides infrastructure for registration of:
4 *
5 * Dependents on a QoS value : register requirements
6 * Watchers of QoS value : get notified when target QoS value changes
7 *
8 * This QoS design is best effort based. Dependents register their QoS needs.
9 * Watchers register to keep track of the current QoS needs of the system.
10 *
11 * There are 3 basic classes of QoS parameter: latency, timeout, throughput
12 * each have defined units:
13 * latency: usec
14 * timeout: usec <-- currently not used.
15 * throughput: kbs (kilo byte / sec)
16 *
17 * There are lists of pm_qos_objects each one wrapping requirements, notifiers
18 *
19 * User mode requirements on a QOS parameter register themselves to the
20 * subsystem by opening the device node /dev/... and writing there request to
21 * the node. As long as the process holds a file handle open to the node the
22 * client continues to be accounted for. Upon file release the usermode
23 * requirement is removed and a new qos target is computed. This way when the
24 * requirement that the application has is cleaned up when closes the file
25 * pointer or exits the pm_qos_object will get an opportunity to clean up.
26 *
27 * mark gross mgross@linux.intel.com
28 */
29
30#include <linux/pm_qos_params.h>
31#include <linux/sched.h>
32#include <linux/spinlock.h>
33#include <linux/slab.h>
34#include <linux/time.h>
35#include <linux/fs.h>
36#include <linux/device.h>
37#include <linux/miscdevice.h>
38#include <linux/string.h>
39#include <linux/platform_device.h>
40#include <linux/init.h>
41
42#include <linux/uaccess.h>
43
44/*
45 * locking rule: all changes to target_value or requirements or notifiers lists
46 * or pm_qos_object list and pm_qos_objects need to happen with pm_qos_lock
47 * held, taken with _irqsave. One lock to rule them all
48 */
49struct requirement_list {
50 struct list_head list;
51 union {
52 s32 value;
53 s32 usec;
54 s32 kbps;
55 };
56 char *name;
57};
58
59static s32 max_compare(s32 v1, s32 v2);
60static s32 min_compare(s32 v1, s32 v2);
61
62struct pm_qos_object {
63 struct requirement_list requirements;
64 struct blocking_notifier_head *notifiers;
65 struct miscdevice pm_qos_power_miscdev;
66 char *name;
67 s32 default_value;
68 s32 target_value;
69 s32 (*comparitor)(s32, s32);
70};
71
72static struct pm_qos_object null_pm_qos;
73static BLOCKING_NOTIFIER_HEAD(cpu_dma_lat_notifier);
74static struct pm_qos_object cpu_dma_pm_qos = {
75 .requirements = {LIST_HEAD_INIT(cpu_dma_pm_qos.requirements.list)},
76 .notifiers = &cpu_dma_lat_notifier,
77 .name = "cpu_dma_latency",
78 .default_value = 2000 * USEC_PER_SEC,
79 .target_value = 2000 * USEC_PER_SEC,
80 .comparitor = min_compare
81};
82
83static BLOCKING_NOTIFIER_HEAD(network_lat_notifier);
84static struct pm_qos_object network_lat_pm_qos = {
85 .requirements = {LIST_HEAD_INIT(network_lat_pm_qos.requirements.list)},
86 .notifiers = &network_lat_notifier,
87 .name = "network_latency",
88 .default_value = 2000 * USEC_PER_SEC,
89 .target_value = 2000 * USEC_PER_SEC,
90 .comparitor = min_compare
91};
92
93
94static BLOCKING_NOTIFIER_HEAD(network_throughput_notifier);
95static struct pm_qos_object network_throughput_pm_qos = {
96 .requirements =
97 {LIST_HEAD_INIT(network_throughput_pm_qos.requirements.list)},
98 .notifiers = &network_throughput_notifier,
99 .name = "network_throughput",
100 .default_value = 0,
101 .target_value = 0,
102 .comparitor = max_compare
103};
104
105
106static struct pm_qos_object *pm_qos_array[] = {
107 &null_pm_qos,
108 &cpu_dma_pm_qos,
109 &network_lat_pm_qos,
110 &network_throughput_pm_qos
111};
112
113static DEFINE_SPINLOCK(pm_qos_lock);
114
115static ssize_t pm_qos_power_write(struct file *filp, const char __user *buf,
116 size_t count, loff_t *f_pos);
117static int pm_qos_power_open(struct inode *inode, struct file *filp);
118static int pm_qos_power_release(struct inode *inode, struct file *filp);
119
120static const struct file_operations pm_qos_power_fops = {
121 .write = pm_qos_power_write,
122 .open = pm_qos_power_open,
123 .release = pm_qos_power_release,
124};
125
126/* static helper functions */
127static s32 max_compare(s32 v1, s32 v2)
128{
129 return max(v1, v2);
130}
131
132static s32 min_compare(s32 v1, s32 v2)
133{
134 return min(v1, v2);
135}
136
137
138static void update_target(int target)
139{
140 s32 extreme_value;
141 struct requirement_list *node;
142 unsigned long flags;
143 int call_notifier = 0;
144
145 spin_lock_irqsave(&pm_qos_lock, flags);
146 extreme_value = pm_qos_array[target]->default_value;
147 list_for_each_entry(node,
148 &pm_qos_array[target]->requirements.list, list) {
149 extreme_value = pm_qos_array[target]->comparitor(
150 extreme_value, node->value);
151 }
152 if (pm_qos_array[target]->target_value != extreme_value) {
153 call_notifier = 1;
154 pm_qos_array[target]->target_value = extreme_value;
155 pr_debug(KERN_ERR "new target for qos %d is %d\n", target,
156 pm_qos_array[target]->target_value);
157 }
158 spin_unlock_irqrestore(&pm_qos_lock, flags);
159
160 if (call_notifier)
161 blocking_notifier_call_chain(pm_qos_array[target]->notifiers,
162 (unsigned long) extreme_value, NULL);
163}
164
165static int register_pm_qos_misc(struct pm_qos_object *qos)
166{
167 qos->pm_qos_power_miscdev.minor = MISC_DYNAMIC_MINOR;
168 qos->pm_qos_power_miscdev.name = qos->name;
169 qos->pm_qos_power_miscdev.fops = &pm_qos_power_fops;
170
171 return misc_register(&qos->pm_qos_power_miscdev);
172}
173
174static int find_pm_qos_object_by_minor(int minor)
175{
176 int pm_qos_class;
177
178 for (pm_qos_class = 0;
179 pm_qos_class < PM_QOS_NUM_CLASSES; pm_qos_class++) {
180 if (minor ==
181 pm_qos_array[pm_qos_class]->pm_qos_power_miscdev.minor)
182 return pm_qos_class;
183 }
184 return -1;
185}
186
187/**
188 * pm_qos_requirement - returns current system wide qos expectation
189 * @pm_qos_class: identification of which qos value is requested
190 *
191 * This function returns the current target value in an atomic manner.
192 */
193int pm_qos_requirement(int pm_qos_class)
194{
195 int ret_val;
196 unsigned long flags;
197
198 spin_lock_irqsave(&pm_qos_lock, flags);
199 ret_val = pm_qos_array[pm_qos_class]->target_value;
200 spin_unlock_irqrestore(&pm_qos_lock, flags);
201
202 return ret_val;
203}
204EXPORT_SYMBOL_GPL(pm_qos_requirement);
205
206/**
207 * pm_qos_add_requirement - inserts new qos request into the list
208 * @pm_qos_class: identifies which list of qos request to us
209 * @name: identifies the request
210 * @value: defines the qos request
211 *
212 * This function inserts a new entry in the pm_qos_class list of requested qos
213 * performance charactoistics. It recomputes the agregate QoS expectations for
214 * the pm_qos_class of parrameters.
215 */
216int pm_qos_add_requirement(int pm_qos_class, char *name, s32 value)
217{
218 struct requirement_list *dep;
219 unsigned long flags;
220
221 dep = kzalloc(sizeof(struct requirement_list), GFP_KERNEL);
222 if (dep) {
223 if (value == PM_QOS_DEFAULT_VALUE)
224 dep->value = pm_qos_array[pm_qos_class]->default_value;
225 else
226 dep->value = value;
227 dep->name = kstrdup(name, GFP_KERNEL);
228 if (!dep->name)
229 goto cleanup;
230
231 spin_lock_irqsave(&pm_qos_lock, flags);
232 list_add(&dep->list,
233 &pm_qos_array[pm_qos_class]->requirements.list);
234 spin_unlock_irqrestore(&pm_qos_lock, flags);
235 update_target(pm_qos_class);
236
237 return 0;
238 }
239
240cleanup:
241 kfree(dep);
242 return -ENOMEM;
243}
244EXPORT_SYMBOL_GPL(pm_qos_add_requirement);
245
246/**
247 * pm_qos_update_requirement - modifies an existing qos request
248 * @pm_qos_class: identifies which list of qos request to us
249 * @name: identifies the request
250 * @value: defines the qos request
251 *
252 * Updates an existing qos requierement for the pm_qos_class of parameters along
253 * with updating the target pm_qos_class value.
254 *
255 * If the named request isn't in the lest then no change is made.
256 */
257int pm_qos_update_requirement(int pm_qos_class, char *name, s32 new_value)
258{
259 unsigned long flags;
260 struct requirement_list *node;
261 int pending_update = 0;
262
263 spin_lock_irqsave(&pm_qos_lock, flags);
264 list_for_each_entry(node,
265 &pm_qos_array[pm_qos_class]->requirements.list, list) {
266 if (strcmp(node->name, name) == 0) {
267 if (new_value == PM_QOS_DEFAULT_VALUE)
268 node->value =
269 pm_qos_array[pm_qos_class]->default_value;
270 else
271 node->value = new_value;
272 pending_update = 1;
273 break;
274 }
275 }
276 spin_unlock_irqrestore(&pm_qos_lock, flags);
277 if (pending_update)
278 update_target(pm_qos_class);
279
280 return 0;
281}
282EXPORT_SYMBOL_GPL(pm_qos_update_requirement);
283
284/**
285 * pm_qos_remove_requirement - modifies an existing qos request
286 * @pm_qos_class: identifies which list of qos request to us
287 * @name: identifies the request
288 *
289 * Will remove named qos request from pm_qos_class list of parrameters and
290 * recompute the current target value for the pm_qos_class.
291 */
292void pm_qos_remove_requirement(int pm_qos_class, char *name)
293{
294 unsigned long flags;
295 struct requirement_list *node;
296 int pending_update = 0;
297
298 spin_lock_irqsave(&pm_qos_lock, flags);
299 list_for_each_entry(node,
300 &pm_qos_array[pm_qos_class]->requirements.list, list) {
301 if (strcmp(node->name, name) == 0) {
302 kfree(node->name);
303 list_del(&node->list);
304 kfree(node);
305 pending_update = 1;
306 break;
307 }
308 }
309 spin_unlock_irqrestore(&pm_qos_lock, flags);
310 if (pending_update)
311 update_target(pm_qos_class);
312}
313EXPORT_SYMBOL_GPL(pm_qos_remove_requirement);
314
315/**
316 * pm_qos_add_notifier - sets notification entry for changes to target value
317 * @pm_qos_class: identifies which qos target changes should be notified.
318 * @notifier: notifier block managed by caller.
319 *
320 * will register the notifier into a notification chain that gets called
321 * uppon changes to the pm_qos_class target value.
322 */
323 int pm_qos_add_notifier(int pm_qos_class, struct notifier_block *notifier)
324{
325 int retval;
326
327 retval = blocking_notifier_chain_register(
328 pm_qos_array[pm_qos_class]->notifiers, notifier);
329
330 return retval;
331}
332EXPORT_SYMBOL_GPL(pm_qos_add_notifier);
333
334/**
335 * pm_qos_remove_notifier - deletes notification entry from chain.
336 * @pm_qos_class: identifies which qos target changes are notified.
337 * @notifier: notifier block to be removed.
338 *
339 * will remove the notifier from the notification chain that gets called
340 * uppon changes to the pm_qos_class target value.
341 */
342int pm_qos_remove_notifier(int pm_qos_class, struct notifier_block *notifier)
343{
344 int retval;
345
346 retval = blocking_notifier_chain_unregister(
347 pm_qos_array[pm_qos_class]->notifiers, notifier);
348
349 return retval;
350}
351EXPORT_SYMBOL_GPL(pm_qos_remove_notifier);
352
353#define PID_NAME_LEN sizeof("process_1234567890")
354static char name[PID_NAME_LEN];
355
356static int pm_qos_power_open(struct inode *inode, struct file *filp)
357{
358 int ret;
359 long pm_qos_class;
360
361 pm_qos_class = find_pm_qos_object_by_minor(iminor(inode));
362 if (pm_qos_class >= 0) {
363 filp->private_data = (void *)pm_qos_class;
364 sprintf(name, "process_%d", current->pid);
365 ret = pm_qos_add_requirement(pm_qos_class, name,
366 PM_QOS_DEFAULT_VALUE);
367 if (ret >= 0)
368 return 0;
369 }
370
371 return -EPERM;
372}
373
374static int pm_qos_power_release(struct inode *inode, struct file *filp)
375{
376 int pm_qos_class;
377
378 pm_qos_class = (long)filp->private_data;
379 sprintf(name, "process_%d", current->pid);
380 pm_qos_remove_requirement(pm_qos_class, name);
381
382 return 0;
383}
384
385static ssize_t pm_qos_power_write(struct file *filp, const char __user *buf,
386 size_t count, loff_t *f_pos)
387{
388 s32 value;
389 int pm_qos_class;
390
391 pm_qos_class = (long)filp->private_data;
392 if (count != sizeof(s32))
393 return -EINVAL;
394 if (copy_from_user(&value, buf, sizeof(s32)))
395 return -EFAULT;
396 sprintf(name, "process_%d", current->pid);
397 pm_qos_update_requirement(pm_qos_class, name, value);
398
399 return sizeof(s32);
400}
401
402
403static int __init pm_qos_power_init(void)
404{
405 int ret = 0;
406
407 ret = register_pm_qos_misc(&cpu_dma_pm_qos);
408 if (ret < 0) {
409 printk(KERN_ERR "pm_qos_param: cpu_dma_latency setup failed\n");
410 return ret;
411 }
412 ret = register_pm_qos_misc(&network_lat_pm_qos);
413 if (ret < 0) {
414 printk(KERN_ERR "pm_qos_param: network_latency setup failed\n");
415 return ret;
416 }
417 ret = register_pm_qos_misc(&network_throughput_pm_qos);
418 if (ret < 0)
419 printk(KERN_ERR
420 "pm_qos_param: network_throughput setup failed\n");
421
422 return ret;
423}
424
425late_initcall(pm_qos_power_init);