Commit | Line | Data |
---|---|---|
d82b3518 MG |
1 | /* |
2 | * This module exposes the interface to kernel space for specifying | |
3 | * QoS dependencies. It provides infrastructure for registration of: | |
4 | * | |
ed77134b | 5 | * Dependents on a QoS value : register requests |
d82b3518 MG |
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 | * | |
ed77134b | 17 | * There are lists of pm_qos_objects each one wrapping requests, notifiers |
d82b3518 | 18 | * |
ed77134b | 19 | * User mode requests on a QOS parameter register themselves to the |
d82b3518 MG |
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 | |
ed77134b MG |
23 | * request is removed and a new qos target is computed. This way when the |
24 | * request that the application has is cleaned up when closes the file | |
d82b3518 MG |
25 | * pointer or exits the pm_qos_object will get an opportunity to clean up. |
26 | * | |
bf1db69f | 27 | * Mark Gross <mgross@linux.intel.com> |
d82b3518 MG |
28 | */ |
29 | ||
ed77134b MG |
30 | /*#define DEBUG*/ |
31 | ||
e8db0be1 | 32 | #include <linux/pm_qos.h> |
d82b3518 MG |
33 | #include <linux/sched.h> |
34 | #include <linux/spinlock.h> | |
35 | #include <linux/slab.h> | |
36 | #include <linux/time.h> | |
37 | #include <linux/fs.h> | |
38 | #include <linux/device.h> | |
39 | #include <linux/miscdevice.h> | |
40 | #include <linux/string.h> | |
41 | #include <linux/platform_device.h> | |
42 | #include <linux/init.h> | |
0775a60a | 43 | #include <linux/kernel.h> |
d82b3518 MG |
44 | |
45 | #include <linux/uaccess.h> | |
6e5fdeed | 46 | #include <linux/export.h> |
247e9ee0 | 47 | #include <trace/events/power.h> |
d82b3518 MG |
48 | |
49 | /* | |
cc749986 | 50 | * locking rule: all changes to constraints or notifiers lists |
d82b3518 MG |
51 | * or pm_qos_object list and pm_qos_objects need to happen with pm_qos_lock |
52 | * held, taken with _irqsave. One lock to rule them all | |
53 | */ | |
d82b3518 | 54 | struct pm_qos_object { |
4e1779ba | 55 | struct pm_qos_constraints *constraints; |
d82b3518 MG |
56 | struct miscdevice pm_qos_power_miscdev; |
57 | char *name; | |
d82b3518 MG |
58 | }; |
59 | ||
5f279845 JB |
60 | static DEFINE_SPINLOCK(pm_qos_lock); |
61 | ||
d82b3518 | 62 | static struct pm_qos_object null_pm_qos; |
4e1779ba | 63 | |
d82b3518 | 64 | static BLOCKING_NOTIFIER_HEAD(cpu_dma_lat_notifier); |
4e1779ba JP |
65 | static struct pm_qos_constraints cpu_dma_constraints = { |
66 | .list = PLIST_HEAD_INIT(cpu_dma_constraints.list), | |
333c5ae9 TC |
67 | .target_value = PM_QOS_CPU_DMA_LAT_DEFAULT_VALUE, |
68 | .default_value = PM_QOS_CPU_DMA_LAT_DEFAULT_VALUE, | |
5f279845 | 69 | .type = PM_QOS_MIN, |
4e1779ba JP |
70 | .notifiers = &cpu_dma_lat_notifier, |
71 | }; | |
72 | static struct pm_qos_object cpu_dma_pm_qos = { | |
73 | .constraints = &cpu_dma_constraints, | |
a6f05b97 | 74 | .name = "cpu_dma_latency", |
d82b3518 MG |
75 | }; |
76 | ||
77 | static BLOCKING_NOTIFIER_HEAD(network_lat_notifier); | |
4e1779ba JP |
78 | static struct pm_qos_constraints network_lat_constraints = { |
79 | .list = PLIST_HEAD_INIT(network_lat_constraints.list), | |
333c5ae9 TC |
80 | .target_value = PM_QOS_NETWORK_LAT_DEFAULT_VALUE, |
81 | .default_value = PM_QOS_NETWORK_LAT_DEFAULT_VALUE, | |
4e1779ba JP |
82 | .type = PM_QOS_MIN, |
83 | .notifiers = &network_lat_notifier, | |
84 | }; | |
85 | static struct pm_qos_object network_lat_pm_qos = { | |
86 | .constraints = &network_lat_constraints, | |
87 | .name = "network_latency", | |
d82b3518 MG |
88 | }; |
89 | ||
90 | ||
91 | static BLOCKING_NOTIFIER_HEAD(network_throughput_notifier); | |
4e1779ba JP |
92 | static struct pm_qos_constraints network_tput_constraints = { |
93 | .list = PLIST_HEAD_INIT(network_tput_constraints.list), | |
333c5ae9 TC |
94 | .target_value = PM_QOS_NETWORK_THROUGHPUT_DEFAULT_VALUE, |
95 | .default_value = PM_QOS_NETWORK_THROUGHPUT_DEFAULT_VALUE, | |
5f279845 | 96 | .type = PM_QOS_MAX, |
4e1779ba JP |
97 | .notifiers = &network_throughput_notifier, |
98 | }; | |
99 | static struct pm_qos_object network_throughput_pm_qos = { | |
100 | .constraints = &network_tput_constraints, | |
101 | .name = "network_throughput", | |
d82b3518 MG |
102 | }; |
103 | ||
104 | ||
105 | static struct pm_qos_object *pm_qos_array[] = { | |
106 | &null_pm_qos, | |
107 | &cpu_dma_pm_qos, | |
108 | &network_lat_pm_qos, | |
109 | &network_throughput_pm_qos | |
110 | }; | |
111 | ||
d82b3518 MG |
112 | static ssize_t pm_qos_power_write(struct file *filp, const char __user *buf, |
113 | size_t count, loff_t *f_pos); | |
f9b9e806 TR |
114 | static ssize_t pm_qos_power_read(struct file *filp, char __user *buf, |
115 | size_t count, loff_t *f_pos); | |
d82b3518 MG |
116 | static int pm_qos_power_open(struct inode *inode, struct file *filp); |
117 | static int pm_qos_power_release(struct inode *inode, struct file *filp); | |
118 | ||
119 | static const struct file_operations pm_qos_power_fops = { | |
120 | .write = pm_qos_power_write, | |
f9b9e806 | 121 | .read = pm_qos_power_read, |
d82b3518 MG |
122 | .open = pm_qos_power_open, |
123 | .release = pm_qos_power_release, | |
6038f373 | 124 | .llseek = noop_llseek, |
d82b3518 MG |
125 | }; |
126 | ||
5f279845 | 127 | /* unlocked internal variant */ |
abe98ec2 | 128 | static inline int pm_qos_get_value(struct pm_qos_constraints *c) |
d82b3518 | 129 | { |
abe98ec2 JP |
130 | if (plist_head_empty(&c->list)) |
131 | return c->default_value; | |
d82b3518 | 132 | |
abe98ec2 | 133 | switch (c->type) { |
5f279845 | 134 | case PM_QOS_MIN: |
abe98ec2 | 135 | return plist_first(&c->list)->prio; |
d82b3518 | 136 | |
5f279845 | 137 | case PM_QOS_MAX: |
abe98ec2 | 138 | return plist_last(&c->list)->prio; |
d82b3518 | 139 | |
5f279845 JB |
140 | default: |
141 | /* runtime check for not using enum */ | |
142 | BUG(); | |
c6a57bff | 143 | return PM_QOS_DEFAULT_VALUE; |
5f279845 JB |
144 | } |
145 | } | |
146 | ||
b66213cd | 147 | s32 pm_qos_read_value(struct pm_qos_constraints *c) |
333c5ae9 | 148 | { |
abe98ec2 | 149 | return c->target_value; |
333c5ae9 TC |
150 | } |
151 | ||
abe98ec2 | 152 | static inline void pm_qos_set_value(struct pm_qos_constraints *c, s32 value) |
333c5ae9 | 153 | { |
abe98ec2 | 154 | c->target_value = value; |
333c5ae9 TC |
155 | } |
156 | ||
abe98ec2 JP |
157 | /** |
158 | * pm_qos_update_target - manages the constraints list and calls the notifiers | |
159 | * if needed | |
160 | * @c: constraints data struct | |
161 | * @node: request to add to the list, to update or to remove | |
162 | * @action: action to take on the constraints list | |
163 | * @value: value of the request to add or update | |
164 | * | |
165 | * This function returns 1 if the aggregated constraint value has changed, 0 | |
166 | * otherwise. | |
167 | */ | |
168 | int pm_qos_update_target(struct pm_qos_constraints *c, struct plist_node *node, | |
169 | enum pm_qos_req_action action, int value) | |
d82b3518 | 170 | { |
d82b3518 | 171 | unsigned long flags; |
abe98ec2 | 172 | int prev_value, curr_value, new_value; |
d82b3518 MG |
173 | |
174 | spin_lock_irqsave(&pm_qos_lock, flags); | |
abe98ec2 JP |
175 | prev_value = pm_qos_get_value(c); |
176 | if (value == PM_QOS_DEFAULT_VALUE) | |
177 | new_value = c->default_value; | |
178 | else | |
179 | new_value = value; | |
180 | ||
181 | switch (action) { | |
182 | case PM_QOS_REMOVE_REQ: | |
183 | plist_del(node, &c->list); | |
184 | break; | |
185 | case PM_QOS_UPDATE_REQ: | |
5f279845 JB |
186 | /* |
187 | * to change the list, we atomically remove, reinit | |
188 | * with new value and add, then see if the extremal | |
189 | * changed | |
190 | */ | |
abe98ec2 JP |
191 | plist_del(node, &c->list); |
192 | case PM_QOS_ADD_REQ: | |
193 | plist_node_init(node, new_value); | |
194 | plist_add(node, &c->list); | |
195 | break; | |
196 | default: | |
197 | /* no action */ | |
198 | ; | |
d82b3518 | 199 | } |
abe98ec2 JP |
200 | |
201 | curr_value = pm_qos_get_value(c); | |
202 | pm_qos_set_value(c, curr_value); | |
203 | ||
d82b3518 MG |
204 | spin_unlock_irqrestore(&pm_qos_lock, flags); |
205 | ||
247e9ee0 | 206 | trace_pm_qos_update_target(action, prev_value, curr_value); |
abe98ec2 JP |
207 | if (prev_value != curr_value) { |
208 | blocking_notifier_call_chain(c->notifiers, | |
5f279845 JB |
209 | (unsigned long)curr_value, |
210 | NULL); | |
abe98ec2 JP |
211 | return 1; |
212 | } else { | |
213 | return 0; | |
214 | } | |
d82b3518 MG |
215 | } |
216 | ||
5efbe427 RW |
217 | /** |
218 | * pm_qos_flags_remove_req - Remove device PM QoS flags request. | |
219 | * @pqf: Device PM QoS flags set to remove the request from. | |
220 | * @req: Request to remove from the set. | |
221 | */ | |
222 | static void pm_qos_flags_remove_req(struct pm_qos_flags *pqf, | |
223 | struct pm_qos_flags_request *req) | |
224 | { | |
225 | s32 val = 0; | |
226 | ||
227 | list_del(&req->node); | |
228 | list_for_each_entry(req, &pqf->list, node) | |
229 | val |= req->flags; | |
230 | ||
231 | pqf->effective_flags = val; | |
232 | } | |
233 | ||
234 | /** | |
235 | * pm_qos_update_flags - Update a set of PM QoS flags. | |
236 | * @pqf: Set of flags to update. | |
237 | * @req: Request to add to the set, to modify, or to remove from the set. | |
238 | * @action: Action to take on the set. | |
239 | * @val: Value of the request to add or modify. | |
240 | * | |
241 | * Update the given set of PM QoS flags and call notifiers if the aggregate | |
242 | * value has changed. Returns 1 if the aggregate constraint value has changed, | |
243 | * 0 otherwise. | |
244 | */ | |
245 | bool pm_qos_update_flags(struct pm_qos_flags *pqf, | |
246 | struct pm_qos_flags_request *req, | |
247 | enum pm_qos_req_action action, s32 val) | |
248 | { | |
249 | unsigned long irqflags; | |
250 | s32 prev_value, curr_value; | |
251 | ||
252 | spin_lock_irqsave(&pm_qos_lock, irqflags); | |
253 | ||
254 | prev_value = list_empty(&pqf->list) ? 0 : pqf->effective_flags; | |
255 | ||
256 | switch (action) { | |
257 | case PM_QOS_REMOVE_REQ: | |
258 | pm_qos_flags_remove_req(pqf, req); | |
259 | break; | |
260 | case PM_QOS_UPDATE_REQ: | |
261 | pm_qos_flags_remove_req(pqf, req); | |
262 | case PM_QOS_ADD_REQ: | |
263 | req->flags = val; | |
264 | INIT_LIST_HEAD(&req->node); | |
265 | list_add_tail(&req->node, &pqf->list); | |
266 | pqf->effective_flags |= val; | |
267 | break; | |
268 | default: | |
269 | /* no action */ | |
270 | ; | |
271 | } | |
272 | ||
273 | curr_value = list_empty(&pqf->list) ? 0 : pqf->effective_flags; | |
274 | ||
275 | spin_unlock_irqrestore(&pm_qos_lock, irqflags); | |
276 | ||
247e9ee0 | 277 | trace_pm_qos_update_flags(action, prev_value, curr_value); |
5efbe427 RW |
278 | return prev_value != curr_value; |
279 | } | |
280 | ||
d82b3518 | 281 | /** |
ed77134b | 282 | * pm_qos_request - returns current system wide qos expectation |
d82b3518 MG |
283 | * @pm_qos_class: identification of which qos value is requested |
284 | * | |
333c5ae9 | 285 | * This function returns the current target value. |
d82b3518 | 286 | */ |
ed77134b | 287 | int pm_qos_request(int pm_qos_class) |
d82b3518 | 288 | { |
abe98ec2 | 289 | return pm_qos_read_value(pm_qos_array[pm_qos_class]->constraints); |
d82b3518 | 290 | } |
ed77134b | 291 | EXPORT_SYMBOL_GPL(pm_qos_request); |
d82b3518 | 292 | |
cc749986 | 293 | int pm_qos_request_active(struct pm_qos_request *req) |
82f68251 JB |
294 | { |
295 | return req->pm_qos_class != 0; | |
296 | } | |
297 | EXPORT_SYMBOL_GPL(pm_qos_request_active); | |
298 | ||
40fea92f SB |
299 | static void __pm_qos_update_request(struct pm_qos_request *req, |
300 | s32 new_value) | |
301 | { | |
302 | trace_pm_qos_update_request(req->pm_qos_class, new_value); | |
303 | ||
304 | if (new_value != req->node.prio) | |
305 | pm_qos_update_target( | |
306 | pm_qos_array[req->pm_qos_class]->constraints, | |
307 | &req->node, PM_QOS_UPDATE_REQ, new_value); | |
308 | } | |
309 | ||
c4772d19 MH |
310 | /** |
311 | * pm_qos_work_fn - the timeout handler of pm_qos_update_request_timeout | |
312 | * @work: work struct for the delayed work (timeout) | |
313 | * | |
314 | * This cancels the timeout request by falling back to the default at timeout. | |
315 | */ | |
316 | static void pm_qos_work_fn(struct work_struct *work) | |
317 | { | |
318 | struct pm_qos_request *req = container_of(to_delayed_work(work), | |
319 | struct pm_qos_request, | |
320 | work); | |
321 | ||
40fea92f | 322 | __pm_qos_update_request(req, PM_QOS_DEFAULT_VALUE); |
c4772d19 MH |
323 | } |
324 | ||
d82b3518 | 325 | /** |
ed77134b | 326 | * pm_qos_add_request - inserts new qos request into the list |
cc749986 | 327 | * @req: pointer to a preallocated handle |
25cc69ec | 328 | * @pm_qos_class: identifies which list of qos request to use |
d82b3518 MG |
329 | * @value: defines the qos request |
330 | * | |
331 | * This function inserts a new entry in the pm_qos_class list of requested qos | |
bf1db69f | 332 | * performance characteristics. It recomputes the aggregate QoS expectations |
cc749986 | 333 | * for the pm_qos_class of parameters and initializes the pm_qos_request |
25cc69ec SK |
334 | * handle. Caller needs to save this handle for later use in updates and |
335 | * removal. | |
d82b3518 | 336 | */ |
25cc69ec | 337 | |
cc749986 | 338 | void pm_qos_add_request(struct pm_qos_request *req, |
82f68251 | 339 | int pm_qos_class, s32 value) |
d82b3518 | 340 | { |
abe98ec2 JP |
341 | if (!req) /*guard against callers passing in null */ |
342 | return; | |
d82b3518 | 343 | |
cc749986 | 344 | if (pm_qos_request_active(req)) { |
82f68251 JB |
345 | WARN(1, KERN_ERR "pm_qos_add_request() called for already added request\n"); |
346 | return; | |
347 | } | |
cc749986 | 348 | req->pm_qos_class = pm_qos_class; |
c4772d19 | 349 | INIT_DELAYED_WORK(&req->work, pm_qos_work_fn); |
ae8822b8 | 350 | trace_pm_qos_add_request(pm_qos_class, value); |
abe98ec2 JP |
351 | pm_qos_update_target(pm_qos_array[pm_qos_class]->constraints, |
352 | &req->node, PM_QOS_ADD_REQ, value); | |
d82b3518 | 353 | } |
ed77134b | 354 | EXPORT_SYMBOL_GPL(pm_qos_add_request); |
d82b3518 MG |
355 | |
356 | /** | |
ed77134b | 357 | * pm_qos_update_request - modifies an existing qos request |
cc749986 | 358 | * @req : handle to list element holding a pm_qos request to use |
d82b3518 MG |
359 | * @value: defines the qos request |
360 | * | |
ed77134b | 361 | * Updates an existing qos request for the pm_qos_class of parameters along |
d82b3518 MG |
362 | * with updating the target pm_qos_class value. |
363 | * | |
ed77134b | 364 | * Attempts are made to make this code callable on hot code paths. |
d82b3518 | 365 | */ |
cc749986 | 366 | void pm_qos_update_request(struct pm_qos_request *req, |
5f279845 | 367 | s32 new_value) |
d82b3518 | 368 | { |
cc749986 | 369 | if (!req) /*guard against callers passing in null */ |
5f279845 JB |
370 | return; |
371 | ||
cc749986 | 372 | if (!pm_qos_request_active(req)) { |
82f68251 JB |
373 | WARN(1, KERN_ERR "pm_qos_update_request() called for unknown object\n"); |
374 | return; | |
375 | } | |
376 | ||
ed1ac6e9 | 377 | cancel_delayed_work_sync(&req->work); |
40fea92f | 378 | __pm_qos_update_request(req, new_value); |
d82b3518 | 379 | } |
ed77134b | 380 | EXPORT_SYMBOL_GPL(pm_qos_update_request); |
d82b3518 | 381 | |
c4772d19 MH |
382 | /** |
383 | * pm_qos_update_request_timeout - modifies an existing qos request temporarily. | |
384 | * @req : handle to list element holding a pm_qos request to use | |
385 | * @new_value: defines the temporal qos request | |
386 | * @timeout_us: the effective duration of this qos request in usecs. | |
387 | * | |
388 | * After timeout_us, this qos request is cancelled automatically. | |
389 | */ | |
390 | void pm_qos_update_request_timeout(struct pm_qos_request *req, s32 new_value, | |
391 | unsigned long timeout_us) | |
392 | { | |
393 | if (!req) | |
394 | return; | |
395 | if (WARN(!pm_qos_request_active(req), | |
396 | "%s called for unknown object.", __func__)) | |
397 | return; | |
398 | ||
ed1ac6e9 | 399 | cancel_delayed_work_sync(&req->work); |
c4772d19 | 400 | |
ae8822b8 S |
401 | trace_pm_qos_update_request_timeout(req->pm_qos_class, |
402 | new_value, timeout_us); | |
c4772d19 MH |
403 | if (new_value != req->node.prio) |
404 | pm_qos_update_target( | |
405 | pm_qos_array[req->pm_qos_class]->constraints, | |
406 | &req->node, PM_QOS_UPDATE_REQ, new_value); | |
407 | ||
408 | schedule_delayed_work(&req->work, usecs_to_jiffies(timeout_us)); | |
409 | } | |
410 | ||
d82b3518 | 411 | /** |
ed77134b | 412 | * pm_qos_remove_request - modifies an existing qos request |
cc749986 | 413 | * @req: handle to request list element |
d82b3518 | 414 | * |
cc749986 | 415 | * Will remove pm qos request from the list of constraints and |
ed77134b MG |
416 | * recompute the current target value for the pm_qos_class. Call this |
417 | * on slow code paths. | |
d82b3518 | 418 | */ |
cc749986 | 419 | void pm_qos_remove_request(struct pm_qos_request *req) |
d82b3518 | 420 | { |
abe98ec2 | 421 | if (!req) /*guard against callers passing in null */ |
ed77134b MG |
422 | return; |
423 | /* silent return to keep pcm code cleaner */ | |
d82b3518 | 424 | |
cc749986 | 425 | if (!pm_qos_request_active(req)) { |
82f68251 JB |
426 | WARN(1, KERN_ERR "pm_qos_remove_request() called for unknown object\n"); |
427 | return; | |
428 | } | |
429 | ||
ed1ac6e9 | 430 | cancel_delayed_work_sync(&req->work); |
c4772d19 | 431 | |
ae8822b8 | 432 | trace_pm_qos_remove_request(req->pm_qos_class, PM_QOS_DEFAULT_VALUE); |
abe98ec2 JP |
433 | pm_qos_update_target(pm_qos_array[req->pm_qos_class]->constraints, |
434 | &req->node, PM_QOS_REMOVE_REQ, | |
435 | PM_QOS_DEFAULT_VALUE); | |
cc749986 | 436 | memset(req, 0, sizeof(*req)); |
d82b3518 | 437 | } |
ed77134b | 438 | EXPORT_SYMBOL_GPL(pm_qos_remove_request); |
d82b3518 MG |
439 | |
440 | /** | |
441 | * pm_qos_add_notifier - sets notification entry for changes to target value | |
442 | * @pm_qos_class: identifies which qos target changes should be notified. | |
443 | * @notifier: notifier block managed by caller. | |
444 | * | |
445 | * will register the notifier into a notification chain that gets called | |
bf1db69f | 446 | * upon changes to the pm_qos_class target value. |
d82b3518 | 447 | */ |
ed77134b | 448 | int pm_qos_add_notifier(int pm_qos_class, struct notifier_block *notifier) |
d82b3518 MG |
449 | { |
450 | int retval; | |
451 | ||
452 | retval = blocking_notifier_chain_register( | |
4e1779ba JP |
453 | pm_qos_array[pm_qos_class]->constraints->notifiers, |
454 | notifier); | |
d82b3518 MG |
455 | |
456 | return retval; | |
457 | } | |
458 | EXPORT_SYMBOL_GPL(pm_qos_add_notifier); | |
459 | ||
460 | /** | |
461 | * pm_qos_remove_notifier - deletes notification entry from chain. | |
462 | * @pm_qos_class: identifies which qos target changes are notified. | |
463 | * @notifier: notifier block to be removed. | |
464 | * | |
465 | * will remove the notifier from the notification chain that gets called | |
bf1db69f | 466 | * upon changes to the pm_qos_class target value. |
d82b3518 MG |
467 | */ |
468 | int pm_qos_remove_notifier(int pm_qos_class, struct notifier_block *notifier) | |
469 | { | |
470 | int retval; | |
471 | ||
472 | retval = blocking_notifier_chain_unregister( | |
4e1779ba JP |
473 | pm_qos_array[pm_qos_class]->constraints->notifiers, |
474 | notifier); | |
d82b3518 MG |
475 | |
476 | return retval; | |
477 | } | |
478 | EXPORT_SYMBOL_GPL(pm_qos_remove_notifier); | |
479 | ||
4a31a334 JP |
480 | /* User space interface to PM QoS classes via misc devices */ |
481 | static int register_pm_qos_misc(struct pm_qos_object *qos) | |
482 | { | |
483 | qos->pm_qos_power_miscdev.minor = MISC_DYNAMIC_MINOR; | |
484 | qos->pm_qos_power_miscdev.name = qos->name; | |
485 | qos->pm_qos_power_miscdev.fops = &pm_qos_power_fops; | |
486 | ||
487 | return misc_register(&qos->pm_qos_power_miscdev); | |
488 | } | |
489 | ||
490 | static int find_pm_qos_object_by_minor(int minor) | |
491 | { | |
492 | int pm_qos_class; | |
493 | ||
d24c2a4f | 494 | for (pm_qos_class = PM_QOS_CPU_DMA_LATENCY; |
4a31a334 JP |
495 | pm_qos_class < PM_QOS_NUM_CLASSES; pm_qos_class++) { |
496 | if (minor == | |
497 | pm_qos_array[pm_qos_class]->pm_qos_power_miscdev.minor) | |
498 | return pm_qos_class; | |
499 | } | |
500 | return -1; | |
501 | } | |
502 | ||
d82b3518 MG |
503 | static int pm_qos_power_open(struct inode *inode, struct file *filp) |
504 | { | |
d82b3518 MG |
505 | long pm_qos_class; |
506 | ||
507 | pm_qos_class = find_pm_qos_object_by_minor(iminor(inode)); | |
d24c2a4f | 508 | if (pm_qos_class >= PM_QOS_CPU_DMA_LATENCY) { |
cc749986 | 509 | struct pm_qos_request *req = kzalloc(sizeof(*req), GFP_KERNEL); |
82f68251 JB |
510 | if (!req) |
511 | return -ENOMEM; | |
512 | ||
513 | pm_qos_add_request(req, pm_qos_class, PM_QOS_DEFAULT_VALUE); | |
514 | filp->private_data = req; | |
ed77134b | 515 | |
6513fd69 | 516 | return 0; |
d82b3518 | 517 | } |
d82b3518 MG |
518 | return -EPERM; |
519 | } | |
520 | ||
521 | static int pm_qos_power_release(struct inode *inode, struct file *filp) | |
522 | { | |
cc749986 | 523 | struct pm_qos_request *req; |
d82b3518 | 524 | |
82f68251 | 525 | req = filp->private_data; |
ed77134b | 526 | pm_qos_remove_request(req); |
82f68251 | 527 | kfree(req); |
d82b3518 MG |
528 | |
529 | return 0; | |
530 | } | |
531 | ||
ed77134b | 532 | |
f9b9e806 TR |
533 | static ssize_t pm_qos_power_read(struct file *filp, char __user *buf, |
534 | size_t count, loff_t *f_pos) | |
535 | { | |
536 | s32 value; | |
537 | unsigned long flags; | |
cc749986 | 538 | struct pm_qos_request *req = filp->private_data; |
f9b9e806 | 539 | |
cc749986 | 540 | if (!req) |
f9b9e806 | 541 | return -EINVAL; |
cc749986 | 542 | if (!pm_qos_request_active(req)) |
f9b9e806 TR |
543 | return -EINVAL; |
544 | ||
f9b9e806 | 545 | spin_lock_irqsave(&pm_qos_lock, flags); |
abe98ec2 | 546 | value = pm_qos_get_value(pm_qos_array[req->pm_qos_class]->constraints); |
f9b9e806 TR |
547 | spin_unlock_irqrestore(&pm_qos_lock, flags); |
548 | ||
549 | return simple_read_from_buffer(buf, count, f_pos, &value, sizeof(s32)); | |
550 | } | |
551 | ||
d82b3518 MG |
552 | static ssize_t pm_qos_power_write(struct file *filp, const char __user *buf, |
553 | size_t count, loff_t *f_pos) | |
554 | { | |
555 | s32 value; | |
cc749986 | 556 | struct pm_qos_request *req; |
ed77134b MG |
557 | |
558 | if (count == sizeof(s32)) { | |
559 | if (copy_from_user(&value, buf, sizeof(s32))) | |
560 | return -EFAULT; | |
d4f7ecf7 | 561 | } else { |
0775a60a RW |
562 | int ret; |
563 | ||
d4f7ecf7 AS |
564 | ret = kstrtos32_from_user(buf, count, 16, &value); |
565 | if (ret) | |
566 | return ret; | |
0775a60a | 567 | } |
d82b3518 | 568 | |
cc749986 JP |
569 | req = filp->private_data; |
570 | pm_qos_update_request(req, value); | |
ed77134b MG |
571 | |
572 | return count; | |
d82b3518 MG |
573 | } |
574 | ||
575 | ||
576 | static int __init pm_qos_power_init(void) | |
577 | { | |
578 | int ret = 0; | |
d031e1de | 579 | int i; |
d82b3518 | 580 | |
d031e1de AF |
581 | BUILD_BUG_ON(ARRAY_SIZE(pm_qos_array) != PM_QOS_NUM_CLASSES); |
582 | ||
d24c2a4f | 583 | for (i = PM_QOS_CPU_DMA_LATENCY; i < PM_QOS_NUM_CLASSES; i++) { |
d031e1de AF |
584 | ret = register_pm_qos_misc(pm_qos_array[i]); |
585 | if (ret < 0) { | |
586 | printk(KERN_ERR "pm_qos_param: %s setup failed\n", | |
587 | pm_qos_array[i]->name); | |
588 | return ret; | |
589 | } | |
d82b3518 | 590 | } |
d82b3518 MG |
591 | |
592 | return ret; | |
593 | } | |
594 | ||
595 | late_initcall(pm_qos_power_init); |