diff options
Diffstat (limited to 'kernel/pm_qos_params.c')
-rw-r--r-- | kernel/pm_qos_params.c | 425 |
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 | */ | ||
49 | struct requirement_list { | ||
50 | struct list_head list; | ||
51 | union { | ||
52 | s32 value; | ||
53 | s32 usec; | ||
54 | s32 kbps; | ||
55 | }; | ||
56 | char *name; | ||
57 | }; | ||
58 | |||
59 | static s32 max_compare(s32 v1, s32 v2); | ||
60 | static s32 min_compare(s32 v1, s32 v2); | ||
61 | |||
62 | struct 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 | |||
72 | static struct pm_qos_object null_pm_qos; | ||
73 | static BLOCKING_NOTIFIER_HEAD(cpu_dma_lat_notifier); | ||
74 | static 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 | |||
83 | static BLOCKING_NOTIFIER_HEAD(network_lat_notifier); | ||
84 | static 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 | |||
94 | static BLOCKING_NOTIFIER_HEAD(network_throughput_notifier); | ||
95 | static 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 | |||
106 | static 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 | |||
113 | static DEFINE_SPINLOCK(pm_qos_lock); | ||
114 | |||
115 | static ssize_t pm_qos_power_write(struct file *filp, const char __user *buf, | ||
116 | size_t count, loff_t *f_pos); | ||
117 | static int pm_qos_power_open(struct inode *inode, struct file *filp); | ||
118 | static int pm_qos_power_release(struct inode *inode, struct file *filp); | ||
119 | |||
120 | static 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 */ | ||
127 | static s32 max_compare(s32 v1, s32 v2) | ||
128 | { | ||
129 | return max(v1, v2); | ||
130 | } | ||
131 | |||
132 | static s32 min_compare(s32 v1, s32 v2) | ||
133 | { | ||
134 | return min(v1, v2); | ||
135 | } | ||
136 | |||
137 | |||
138 | static 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 | |||
165 | static 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 | |||
174 | static 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 | */ | ||
193 | int 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 | } | ||
204 | EXPORT_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 | */ | ||
216 | int 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 | |||
240 | cleanup: | ||
241 | kfree(dep); | ||
242 | return -ENOMEM; | ||
243 | } | ||
244 | EXPORT_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 | */ | ||
257 | int 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 | } | ||
282 | EXPORT_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 | */ | ||
292 | void 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 | } | ||
313 | EXPORT_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 | } | ||
332 | EXPORT_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 | */ | ||
342 | int 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 | } | ||
351 | EXPORT_SYMBOL_GPL(pm_qos_remove_notifier); | ||
352 | |||
353 | #define PID_NAME_LEN sizeof("process_1234567890") | ||
354 | static char name[PID_NAME_LEN]; | ||
355 | |||
356 | static 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 | |||
374 | static 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 | |||
385 | static 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 | |||
403 | static 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 | |||
425 | late_initcall(pm_qos_power_init); | ||