]>
Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * processor_perflib.c - ACPI Processor P-States Library ($Revision: 71 $) | |
3 | * | |
4 | * Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com> | |
5 | * Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com> | |
6 | * Copyright (C) 2004 Dominik Brodowski <linux@brodo.de> | |
7 | * Copyright (C) 2004 Anil S Keshavamurthy <anil.s.keshavamurthy@intel.com> | |
8 | * - Added processor hotplug support | |
9 | * | |
10 | * | |
11 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
12 | * | |
13 | * This program is free software; you can redistribute it and/or modify | |
14 | * it under the terms of the GNU General Public License as published by | |
15 | * the Free Software Foundation; either version 2 of the License, or (at | |
16 | * your option) any later version. | |
17 | * | |
18 | * This program is distributed in the hope that it will be useful, but | |
19 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
21 | * General Public License for more details. | |
22 | * | |
23 | * You should have received a copy of the GNU General Public License along | |
24 | * with this program; if not, write to the Free Software Foundation, Inc., | |
25 | * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. | |
26 | * | |
27 | */ | |
28 | ||
1da177e4 LT |
29 | #include <linux/kernel.h> |
30 | #include <linux/module.h> | |
31 | #include <linux/init.h> | |
32 | #include <linux/cpufreq.h> | |
33 | ||
34 | #ifdef CONFIG_X86_ACPI_CPUFREQ_PROC_INTF | |
35 | #include <linux/proc_fs.h> | |
36 | #include <linux/seq_file.h> | |
65c19bbd | 37 | #include <linux/mutex.h> |
1da177e4 LT |
38 | |
39 | #include <asm/uaccess.h> | |
40 | #endif | |
41 | ||
42 | #include <acpi/acpi_bus.h> | |
43 | #include <acpi/processor.h> | |
44 | ||
1da177e4 LT |
45 | #define ACPI_PROCESSOR_COMPONENT 0x01000000 |
46 | #define ACPI_PROCESSOR_CLASS "processor" | |
47 | #define ACPI_PROCESSOR_DRIVER_NAME "ACPI Processor Driver" | |
48 | #define ACPI_PROCESSOR_FILE_PERFORMANCE "performance" | |
49 | #define _COMPONENT ACPI_PROCESSOR_COMPONENT | |
4be44fcd | 50 | ACPI_MODULE_NAME("acpi_processor") |
1da177e4 | 51 | |
65c19bbd | 52 | static DEFINE_MUTEX(performance_mutex); |
1da177e4 LT |
53 | |
54 | /* | |
55 | * _PPC support is implemented as a CPUfreq policy notifier: | |
56 | * This means each time a CPUfreq driver registered also with | |
57 | * the ACPI core is asked to change the speed policy, the maximum | |
58 | * value is adjusted so that it is within the platform limit. | |
59 | * | |
60 | * Also, when a new platform limit value is detected, the CPUfreq | |
61 | * policy is adjusted accordingly. | |
62 | */ | |
63 | ||
64 | #define PPC_REGISTERED 1 | |
65 | #define PPC_IN_USE 2 | |
66 | ||
67 | static int acpi_processor_ppc_status = 0; | |
68 | ||
69 | static int acpi_processor_ppc_notifier(struct notifier_block *nb, | |
4be44fcd | 70 | unsigned long event, void *data) |
1da177e4 LT |
71 | { |
72 | struct cpufreq_policy *policy = data; | |
73 | struct acpi_processor *pr; | |
74 | unsigned int ppc = 0; | |
75 | ||
65c19bbd | 76 | mutex_lock(&performance_mutex); |
1da177e4 LT |
77 | |
78 | if (event != CPUFREQ_INCOMPATIBLE) | |
79 | goto out; | |
80 | ||
81 | pr = processors[policy->cpu]; | |
82 | if (!pr || !pr->performance) | |
83 | goto out; | |
84 | ||
4be44fcd | 85 | ppc = (unsigned int)pr->performance_platform_limit; |
1da177e4 LT |
86 | if (!ppc) |
87 | goto out; | |
88 | ||
89 | if (ppc > pr->performance->state_count) | |
90 | goto out; | |
91 | ||
92 | cpufreq_verify_within_limits(policy, 0, | |
4be44fcd LB |
93 | pr->performance->states[ppc]. |
94 | core_frequency * 1000); | |
1da177e4 | 95 | |
4be44fcd | 96 | out: |
65c19bbd | 97 | mutex_unlock(&performance_mutex); |
1da177e4 LT |
98 | |
99 | return 0; | |
100 | } | |
101 | ||
1da177e4 LT |
102 | static struct notifier_block acpi_ppc_notifier_block = { |
103 | .notifier_call = acpi_processor_ppc_notifier, | |
104 | }; | |
105 | ||
4be44fcd | 106 | static int acpi_processor_get_platform_limit(struct acpi_processor *pr) |
1da177e4 | 107 | { |
4be44fcd LB |
108 | acpi_status status = 0; |
109 | unsigned long ppc = 0; | |
1da177e4 | 110 | |
1da177e4 LT |
111 | |
112 | if (!pr) | |
d550d98d | 113 | return -EINVAL; |
1da177e4 LT |
114 | |
115 | /* | |
116 | * _PPC indicates the maximum state currently supported by the platform | |
117 | * (e.g. 0 = states 0..n; 1 = states 1..n; etc. | |
118 | */ | |
119 | status = acpi_evaluate_integer(pr->handle, "_PPC", NULL, &ppc); | |
120 | ||
121 | if (status != AE_NOT_FOUND) | |
122 | acpi_processor_ppc_status |= PPC_IN_USE; | |
123 | ||
4be44fcd | 124 | if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) { |
a6fc6720 | 125 | ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PPC")); |
d550d98d | 126 | return -ENODEV; |
1da177e4 LT |
127 | } |
128 | ||
4be44fcd | 129 | pr->performance_platform_limit = (int)ppc; |
1da177e4 | 130 | |
d550d98d | 131 | return 0; |
1da177e4 LT |
132 | } |
133 | ||
4be44fcd | 134 | int acpi_processor_ppc_has_changed(struct acpi_processor *pr) |
1da177e4 LT |
135 | { |
136 | int ret = acpi_processor_get_platform_limit(pr); | |
137 | if (ret < 0) | |
138 | return (ret); | |
139 | else | |
140 | return cpufreq_update_policy(pr->id); | |
141 | } | |
142 | ||
4be44fcd LB |
143 | void acpi_processor_ppc_init(void) |
144 | { | |
145 | if (!cpufreq_register_notifier | |
146 | (&acpi_ppc_notifier_block, CPUFREQ_POLICY_NOTIFIER)) | |
1da177e4 LT |
147 | acpi_processor_ppc_status |= PPC_REGISTERED; |
148 | else | |
4be44fcd LB |
149 | printk(KERN_DEBUG |
150 | "Warning: Processor Platform Limit not supported.\n"); | |
1da177e4 LT |
151 | } |
152 | ||
4be44fcd LB |
153 | void acpi_processor_ppc_exit(void) |
154 | { | |
1da177e4 | 155 | if (acpi_processor_ppc_status & PPC_REGISTERED) |
4be44fcd LB |
156 | cpufreq_unregister_notifier(&acpi_ppc_notifier_block, |
157 | CPUFREQ_POLICY_NOTIFIER); | |
1da177e4 LT |
158 | |
159 | acpi_processor_ppc_status &= ~PPC_REGISTERED; | |
160 | } | |
161 | ||
4be44fcd | 162 | static int acpi_processor_get_performance_control(struct acpi_processor *pr) |
1da177e4 | 163 | { |
4be44fcd LB |
164 | int result = 0; |
165 | acpi_status status = 0; | |
166 | struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; | |
167 | union acpi_object *pct = NULL; | |
168 | union acpi_object obj = { 0 }; | |
1da177e4 | 169 | |
1da177e4 LT |
170 | |
171 | status = acpi_evaluate_object(pr->handle, "_PCT", NULL, &buffer); | |
4be44fcd | 172 | if (ACPI_FAILURE(status)) { |
a6fc6720 | 173 | ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PCT")); |
d550d98d | 174 | return -ENODEV; |
1da177e4 LT |
175 | } |
176 | ||
4be44fcd | 177 | pct = (union acpi_object *)buffer.pointer; |
1da177e4 | 178 | if (!pct || (pct->type != ACPI_TYPE_PACKAGE) |
4be44fcd | 179 | || (pct->package.count != 2)) { |
6468463a | 180 | printk(KERN_ERR PREFIX "Invalid _PCT data\n"); |
1da177e4 LT |
181 | result = -EFAULT; |
182 | goto end; | |
183 | } | |
184 | ||
185 | /* | |
186 | * control_register | |
187 | */ | |
188 | ||
189 | obj = pct->package.elements[0]; | |
190 | ||
191 | if ((obj.type != ACPI_TYPE_BUFFER) | |
4be44fcd LB |
192 | || (obj.buffer.length < sizeof(struct acpi_pct_register)) |
193 | || (obj.buffer.pointer == NULL)) { | |
6468463a | 194 | printk(KERN_ERR PREFIX "Invalid _PCT data (control_register)\n"); |
1da177e4 LT |
195 | result = -EFAULT; |
196 | goto end; | |
197 | } | |
4be44fcd LB |
198 | memcpy(&pr->performance->control_register, obj.buffer.pointer, |
199 | sizeof(struct acpi_pct_register)); | |
1da177e4 LT |
200 | |
201 | /* | |
202 | * status_register | |
203 | */ | |
204 | ||
205 | obj = pct->package.elements[1]; | |
206 | ||
207 | if ((obj.type != ACPI_TYPE_BUFFER) | |
4be44fcd LB |
208 | || (obj.buffer.length < sizeof(struct acpi_pct_register)) |
209 | || (obj.buffer.pointer == NULL)) { | |
6468463a | 210 | printk(KERN_ERR PREFIX "Invalid _PCT data (status_register)\n"); |
1da177e4 LT |
211 | result = -EFAULT; |
212 | goto end; | |
213 | } | |
214 | ||
4be44fcd LB |
215 | memcpy(&pr->performance->status_register, obj.buffer.pointer, |
216 | sizeof(struct acpi_pct_register)); | |
1da177e4 | 217 | |
4be44fcd | 218 | end: |
02438d87 | 219 | kfree(buffer.pointer); |
1da177e4 | 220 | |
d550d98d | 221 | return result; |
1da177e4 LT |
222 | } |
223 | ||
4be44fcd | 224 | static int acpi_processor_get_performance_states(struct acpi_processor *pr) |
1da177e4 | 225 | { |
4be44fcd LB |
226 | int result = 0; |
227 | acpi_status status = AE_OK; | |
228 | struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; | |
229 | struct acpi_buffer format = { sizeof("NNNNNN"), "NNNNNN" }; | |
230 | struct acpi_buffer state = { 0, NULL }; | |
231 | union acpi_object *pss = NULL; | |
232 | int i; | |
1da177e4 | 233 | |
1da177e4 LT |
234 | |
235 | status = acpi_evaluate_object(pr->handle, "_PSS", NULL, &buffer); | |
4be44fcd | 236 | if (ACPI_FAILURE(status)) { |
a6fc6720 | 237 | ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PSS")); |
d550d98d | 238 | return -ENODEV; |
1da177e4 LT |
239 | } |
240 | ||
4be44fcd | 241 | pss = (union acpi_object *)buffer.pointer; |
1da177e4 | 242 | if (!pss || (pss->type != ACPI_TYPE_PACKAGE)) { |
6468463a | 243 | printk(KERN_ERR PREFIX "Invalid _PSS data\n"); |
1da177e4 LT |
244 | result = -EFAULT; |
245 | goto end; | |
246 | } | |
247 | ||
248 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Found %d performance states\n", | |
4be44fcd | 249 | pss->package.count)); |
1da177e4 LT |
250 | |
251 | pr->performance->state_count = pss->package.count; | |
4be44fcd LB |
252 | pr->performance->states = |
253 | kmalloc(sizeof(struct acpi_processor_px) * pss->package.count, | |
254 | GFP_KERNEL); | |
1da177e4 LT |
255 | if (!pr->performance->states) { |
256 | result = -ENOMEM; | |
257 | goto end; | |
258 | } | |
259 | ||
260 | for (i = 0; i < pr->performance->state_count; i++) { | |
261 | ||
262 | struct acpi_processor_px *px = &(pr->performance->states[i]); | |
263 | ||
264 | state.length = sizeof(struct acpi_processor_px); | |
265 | state.pointer = px; | |
266 | ||
267 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Extracting state %d\n", i)); | |
268 | ||
269 | status = acpi_extract_package(&(pss->package.elements[i]), | |
4be44fcd | 270 | &format, &state); |
1da177e4 | 271 | if (ACPI_FAILURE(status)) { |
a6fc6720 | 272 | ACPI_EXCEPTION((AE_INFO, status, "Invalid _PSS data")); |
1da177e4 LT |
273 | result = -EFAULT; |
274 | kfree(pr->performance->states); | |
275 | goto end; | |
276 | } | |
277 | ||
278 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, | |
4be44fcd LB |
279 | "State [%d]: core_frequency[%d] power[%d] transition_latency[%d] bus_master_latency[%d] control[0x%x] status[0x%x]\n", |
280 | i, | |
281 | (u32) px->core_frequency, | |
282 | (u32) px->power, | |
283 | (u32) px->transition_latency, | |
284 | (u32) px->bus_master_latency, | |
285 | (u32) px->control, (u32) px->status)); | |
1da177e4 LT |
286 | |
287 | if (!px->core_frequency) { | |
6468463a LB |
288 | printk(KERN_ERR PREFIX |
289 | "Invalid _PSS data: freq is zero\n"); | |
1da177e4 LT |
290 | result = -EFAULT; |
291 | kfree(pr->performance->states); | |
292 | goto end; | |
293 | } | |
294 | } | |
295 | ||
4be44fcd | 296 | end: |
02438d87 | 297 | kfree(buffer.pointer); |
1da177e4 | 298 | |
d550d98d | 299 | return result; |
1da177e4 LT |
300 | } |
301 | ||
4be44fcd | 302 | static int acpi_processor_get_performance_info(struct acpi_processor *pr) |
1da177e4 | 303 | { |
4be44fcd LB |
304 | int result = 0; |
305 | acpi_status status = AE_OK; | |
306 | acpi_handle handle = NULL; | |
1da177e4 | 307 | |
1da177e4 LT |
308 | |
309 | if (!pr || !pr->performance || !pr->handle) | |
d550d98d | 310 | return -EINVAL; |
1da177e4 | 311 | |
1da177e4 LT |
312 | status = acpi_get_handle(pr->handle, "_PCT", &handle); |
313 | if (ACPI_FAILURE(status)) { | |
314 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, | |
4be44fcd | 315 | "ACPI-based processor performance control unavailable\n")); |
d550d98d | 316 | return -ENODEV; |
1da177e4 LT |
317 | } |
318 | ||
319 | result = acpi_processor_get_performance_control(pr); | |
320 | if (result) | |
d550d98d | 321 | return result; |
1da177e4 LT |
322 | |
323 | result = acpi_processor_get_performance_states(pr); | |
324 | if (result) | |
d550d98d | 325 | return result; |
1da177e4 LT |
326 | |
327 | result = acpi_processor_get_platform_limit(pr); | |
328 | if (result) | |
d550d98d | 329 | return result; |
1da177e4 | 330 | |
d550d98d | 331 | return 0; |
1da177e4 LT |
332 | } |
333 | ||
4be44fcd LB |
334 | int acpi_processor_notify_smm(struct module *calling_module) |
335 | { | |
336 | acpi_status status; | |
337 | static int is_done = 0; | |
1da177e4 | 338 | |
1da177e4 LT |
339 | |
340 | if (!(acpi_processor_ppc_status & PPC_REGISTERED)) | |
d550d98d | 341 | return -EBUSY; |
1da177e4 LT |
342 | |
343 | if (!try_module_get(calling_module)) | |
d550d98d | 344 | return -EINVAL; |
1da177e4 LT |
345 | |
346 | /* is_done is set to negative if an error occured, | |
347 | * and to postitive if _no_ error occured, but SMM | |
348 | * was already notified. This avoids double notification | |
349 | * which might lead to unexpected results... | |
350 | */ | |
351 | if (is_done > 0) { | |
352 | module_put(calling_module); | |
d550d98d | 353 | return 0; |
4be44fcd | 354 | } else if (is_done < 0) { |
1da177e4 | 355 | module_put(calling_module); |
d550d98d | 356 | return is_done; |
1da177e4 LT |
357 | } |
358 | ||
359 | is_done = -EIO; | |
360 | ||
361 | /* Can't write pstate_cnt to smi_cmd if either value is zero */ | |
4be44fcd LB |
362 | if ((!acpi_fadt.smi_cmd) || (!acpi_fadt.pstate_cnt)) { |
363 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, "No SMI port or pstate_cnt\n")); | |
1da177e4 | 364 | module_put(calling_module); |
d550d98d | 365 | return 0; |
1da177e4 LT |
366 | } |
367 | ||
4be44fcd LB |
368 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, |
369 | "Writing pstate_cnt [0x%x] to smi_cmd [0x%x]\n", | |
370 | acpi_fadt.pstate_cnt, acpi_fadt.smi_cmd)); | |
1da177e4 LT |
371 | |
372 | /* FADT v1 doesn't support pstate_cnt, many BIOS vendors use | |
373 | * it anyway, so we need to support it... */ | |
374 | if (acpi_fadt_is_v1) { | |
4be44fcd LB |
375 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, |
376 | "Using v1.0 FADT reserved value for pstate_cnt\n")); | |
1da177e4 LT |
377 | } |
378 | ||
4be44fcd LB |
379 | status = acpi_os_write_port(acpi_fadt.smi_cmd, |
380 | (u32) acpi_fadt.pstate_cnt, 8); | |
381 | if (ACPI_FAILURE(status)) { | |
a6fc6720 TR |
382 | ACPI_EXCEPTION((AE_INFO, status, |
383 | "Failed to write pstate_cnt [0x%x] to " | |
384 | "smi_cmd [0x%x]", acpi_fadt.pstate_cnt, | |
385 | acpi_fadt.smi_cmd)); | |
1da177e4 | 386 | module_put(calling_module); |
d550d98d | 387 | return status; |
1da177e4 LT |
388 | } |
389 | ||
390 | /* Success. If there's no _PPC, we need to fear nothing, so | |
391 | * we can allow the cpufreq driver to be rmmod'ed. */ | |
392 | is_done = 1; | |
393 | ||
394 | if (!(acpi_processor_ppc_status & PPC_IN_USE)) | |
395 | module_put(calling_module); | |
396 | ||
d550d98d | 397 | return 0; |
1da177e4 | 398 | } |
1da177e4 | 399 | |
4be44fcd | 400 | EXPORT_SYMBOL(acpi_processor_notify_smm); |
1da177e4 LT |
401 | |
402 | #ifdef CONFIG_X86_ACPI_CPUFREQ_PROC_INTF | |
403 | /* /proc/acpi/processor/../performance interface (DEPRECATED) */ | |
404 | ||
405 | static int acpi_processor_perf_open_fs(struct inode *inode, struct file *file); | |
406 | static struct file_operations acpi_processor_perf_fops = { | |
4be44fcd LB |
407 | .open = acpi_processor_perf_open_fs, |
408 | .read = seq_read, | |
409 | .llseek = seq_lseek, | |
410 | .release = single_release, | |
1da177e4 LT |
411 | }; |
412 | ||
413 | static int acpi_processor_perf_seq_show(struct seq_file *seq, void *offset) | |
414 | { | |
4be44fcd LB |
415 | struct acpi_processor *pr = (struct acpi_processor *)seq->private; |
416 | int i; | |
1da177e4 | 417 | |
1da177e4 LT |
418 | |
419 | if (!pr) | |
420 | goto end; | |
421 | ||
422 | if (!pr->performance) { | |
423 | seq_puts(seq, "<not supported>\n"); | |
424 | goto end; | |
425 | } | |
426 | ||
427 | seq_printf(seq, "state count: %d\n" | |
4be44fcd LB |
428 | "active state: P%d\n", |
429 | pr->performance->state_count, pr->performance->state); | |
1da177e4 LT |
430 | |
431 | seq_puts(seq, "states:\n"); | |
432 | for (i = 0; i < pr->performance->state_count; i++) | |
4be44fcd LB |
433 | seq_printf(seq, |
434 | " %cP%d: %d MHz, %d mW, %d uS\n", | |
435 | (i == pr->performance->state ? '*' : ' '), i, | |
436 | (u32) pr->performance->states[i].core_frequency, | |
437 | (u32) pr->performance->states[i].power, | |
438 | (u32) pr->performance->states[i].transition_latency); | |
439 | ||
440 | end: | |
d550d98d | 441 | return 0; |
1da177e4 LT |
442 | } |
443 | ||
444 | static int acpi_processor_perf_open_fs(struct inode *inode, struct file *file) | |
445 | { | |
446 | return single_open(file, acpi_processor_perf_seq_show, | |
4be44fcd | 447 | PDE(inode)->data); |
1da177e4 LT |
448 | } |
449 | ||
450 | static ssize_t | |
4be44fcd LB |
451 | acpi_processor_write_performance(struct file *file, |
452 | const char __user * buffer, | |
453 | size_t count, loff_t * data) | |
1da177e4 | 454 | { |
4be44fcd LB |
455 | int result = 0; |
456 | struct seq_file *m = (struct seq_file *)file->private_data; | |
457 | struct acpi_processor *pr = (struct acpi_processor *)m->private; | |
1da177e4 | 458 | struct acpi_processor_performance *perf; |
4be44fcd LB |
459 | char state_string[12] = { '\0' }; |
460 | unsigned int new_state = 0; | |
461 | struct cpufreq_policy policy; | |
1da177e4 | 462 | |
1da177e4 LT |
463 | |
464 | if (!pr || (count > sizeof(state_string) - 1)) | |
d550d98d | 465 | return -EINVAL; |
1da177e4 LT |
466 | |
467 | perf = pr->performance; | |
468 | if (!perf) | |
d550d98d | 469 | return -EINVAL; |
1da177e4 LT |
470 | |
471 | if (copy_from_user(state_string, buffer, count)) | |
d550d98d | 472 | return -EFAULT; |
1da177e4 LT |
473 | |
474 | state_string[count] = '\0'; | |
475 | new_state = simple_strtoul(state_string, NULL, 0); | |
476 | ||
477 | if (new_state >= perf->state_count) | |
d550d98d | 478 | return -EINVAL; |
1da177e4 LT |
479 | |
480 | cpufreq_get_policy(&policy, pr->id); | |
481 | ||
482 | policy.cpu = pr->id; | |
483 | policy.min = perf->states[new_state].core_frequency * 1000; | |
484 | policy.max = perf->states[new_state].core_frequency * 1000; | |
485 | ||
486 | result = cpufreq_set_policy(&policy); | |
487 | if (result) | |
d550d98d | 488 | return result; |
1da177e4 | 489 | |
d550d98d | 490 | return count; |
1da177e4 LT |
491 | } |
492 | ||
4be44fcd | 493 | static void acpi_cpufreq_add_file(struct acpi_processor *pr) |
1da177e4 | 494 | { |
4be44fcd LB |
495 | struct proc_dir_entry *entry = NULL; |
496 | struct acpi_device *device = NULL; | |
1da177e4 | 497 | |
1da177e4 LT |
498 | |
499 | if (acpi_bus_get_device(pr->handle, &device)) | |
d550d98d | 500 | return; |
1da177e4 LT |
501 | |
502 | /* add file 'performance' [R/W] */ | |
503 | entry = create_proc_entry(ACPI_PROCESSOR_FILE_PERFORMANCE, | |
4be44fcd LB |
504 | S_IFREG | S_IRUGO | S_IWUSR, |
505 | acpi_device_dir(device)); | |
a6fc6720 | 506 | if (entry){ |
d479e908 | 507 | acpi_processor_perf_fops.write = acpi_processor_write_performance; |
1da177e4 | 508 | entry->proc_fops = &acpi_processor_perf_fops; |
1da177e4 LT |
509 | entry->data = acpi_driver_data(device); |
510 | entry->owner = THIS_MODULE; | |
511 | } | |
d550d98d | 512 | return; |
1da177e4 LT |
513 | } |
514 | ||
4be44fcd | 515 | static void acpi_cpufreq_remove_file(struct acpi_processor *pr) |
1da177e4 | 516 | { |
4be44fcd | 517 | struct acpi_device *device = NULL; |
1da177e4 | 518 | |
1da177e4 LT |
519 | |
520 | if (acpi_bus_get_device(pr->handle, &device)) | |
d550d98d | 521 | return; |
1da177e4 LT |
522 | |
523 | /* remove file 'performance' */ | |
524 | remove_proc_entry(ACPI_PROCESSOR_FILE_PERFORMANCE, | |
4be44fcd | 525 | acpi_device_dir(device)); |
1da177e4 | 526 | |
d550d98d | 527 | return; |
1da177e4 LT |
528 | } |
529 | ||
530 | #else | |
4be44fcd LB |
531 | static void acpi_cpufreq_add_file(struct acpi_processor *pr) |
532 | { | |
533 | return; | |
534 | } | |
535 | static void acpi_cpufreq_remove_file(struct acpi_processor *pr) | |
536 | { | |
537 | return; | |
538 | } | |
539 | #endif /* CONFIG_X86_ACPI_CPUFREQ_PROC_INTF */ | |
1da177e4 | 540 | |
3b2d9942 VP |
541 | static int acpi_processor_get_psd(struct acpi_processor *pr) |
542 | { | |
543 | int result = 0; | |
544 | acpi_status status = AE_OK; | |
545 | struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; | |
546 | struct acpi_buffer format = {sizeof("NNNNN"), "NNNNN"}; | |
547 | struct acpi_buffer state = {0, NULL}; | |
548 | union acpi_object *psd = NULL; | |
549 | struct acpi_psd_package *pdomain; | |
550 | ||
3b2d9942 VP |
551 | status = acpi_evaluate_object(pr->handle, "_PSD", NULL, &buffer); |
552 | if (ACPI_FAILURE(status)) { | |
9011bff4 | 553 | return -ENODEV; |
3b2d9942 VP |
554 | } |
555 | ||
556 | psd = (union acpi_object *) buffer.pointer; | |
557 | if (!psd || (psd->type != ACPI_TYPE_PACKAGE)) { | |
558 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid _PSD data\n")); | |
559 | result = -EFAULT; | |
560 | goto end; | |
561 | } | |
562 | ||
563 | if (psd->package.count != 1) { | |
564 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid _PSD data\n")); | |
565 | result = -EFAULT; | |
566 | goto end; | |
567 | } | |
568 | ||
569 | pdomain = &(pr->performance->domain_info); | |
570 | ||
571 | state.length = sizeof(struct acpi_psd_package); | |
572 | state.pointer = pdomain; | |
573 | ||
574 | status = acpi_extract_package(&(psd->package.elements[0]), | |
575 | &format, &state); | |
576 | if (ACPI_FAILURE(status)) { | |
577 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid _PSD data\n")); | |
578 | result = -EFAULT; | |
579 | goto end; | |
580 | } | |
581 | ||
582 | if (pdomain->num_entries != ACPI_PSD_REV0_ENTRIES) { | |
583 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Unknown _PSD:num_entries\n")); | |
584 | result = -EFAULT; | |
585 | goto end; | |
586 | } | |
587 | ||
588 | if (pdomain->revision != ACPI_PSD_REV0_REVISION) { | |
589 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Unknown _PSD:revision\n")); | |
590 | result = -EFAULT; | |
591 | goto end; | |
592 | } | |
593 | ||
594 | end: | |
02438d87 | 595 | kfree(buffer.pointer); |
9011bff4 | 596 | return result; |
3b2d9942 VP |
597 | } |
598 | ||
599 | int acpi_processor_preregister_performance( | |
600 | struct acpi_processor_performance **performance) | |
601 | { | |
602 | int count, count_target; | |
603 | int retval = 0; | |
604 | unsigned int i, j; | |
605 | cpumask_t covered_cpus; | |
606 | struct acpi_processor *pr; | |
607 | struct acpi_psd_package *pdomain; | |
608 | struct acpi_processor *match_pr; | |
609 | struct acpi_psd_package *match_pdomain; | |
610 | ||
785fcccd | 611 | mutex_lock(&performance_mutex); |
3b2d9942 VP |
612 | |
613 | retval = 0; | |
614 | ||
615 | /* Call _PSD for all CPUs */ | |
193de0c7 | 616 | for_each_possible_cpu(i) { |
3b2d9942 VP |
617 | pr = processors[i]; |
618 | if (!pr) { | |
619 | /* Look only at processors in ACPI namespace */ | |
620 | continue; | |
621 | } | |
622 | ||
623 | if (pr->performance) { | |
624 | retval = -EBUSY; | |
625 | continue; | |
626 | } | |
627 | ||
628 | if (!performance || !performance[i]) { | |
629 | retval = -EINVAL; | |
630 | continue; | |
631 | } | |
632 | ||
633 | pr->performance = performance[i]; | |
634 | cpu_set(i, pr->performance->shared_cpu_map); | |
635 | if (acpi_processor_get_psd(pr)) { | |
636 | retval = -EINVAL; | |
637 | continue; | |
638 | } | |
639 | } | |
640 | if (retval) | |
641 | goto err_ret; | |
642 | ||
643 | /* | |
644 | * Now that we have _PSD data from all CPUs, lets setup P-state | |
645 | * domain info. | |
646 | */ | |
193de0c7 | 647 | for_each_possible_cpu(i) { |
3b2d9942 VP |
648 | pr = processors[i]; |
649 | if (!pr) | |
650 | continue; | |
651 | ||
652 | /* Basic validity check for domain info */ | |
653 | pdomain = &(pr->performance->domain_info); | |
654 | if ((pdomain->revision != ACPI_PSD_REV0_REVISION) || | |
655 | (pdomain->num_entries != ACPI_PSD_REV0_ENTRIES)) { | |
656 | retval = -EINVAL; | |
657 | goto err_ret; | |
658 | } | |
659 | if (pdomain->coord_type != DOMAIN_COORD_TYPE_SW_ALL && | |
660 | pdomain->coord_type != DOMAIN_COORD_TYPE_SW_ANY && | |
661 | pdomain->coord_type != DOMAIN_COORD_TYPE_HW_ALL) { | |
662 | retval = -EINVAL; | |
663 | goto err_ret; | |
664 | } | |
665 | } | |
666 | ||
667 | cpus_clear(covered_cpus); | |
193de0c7 | 668 | for_each_possible_cpu(i) { |
3b2d9942 VP |
669 | pr = processors[i]; |
670 | if (!pr) | |
671 | continue; | |
672 | ||
673 | if (cpu_isset(i, covered_cpus)) | |
674 | continue; | |
675 | ||
676 | pdomain = &(pr->performance->domain_info); | |
677 | cpu_set(i, pr->performance->shared_cpu_map); | |
678 | cpu_set(i, covered_cpus); | |
679 | if (pdomain->num_processors <= 1) | |
680 | continue; | |
681 | ||
682 | /* Validate the Domain info */ | |
683 | count_target = pdomain->num_processors; | |
684 | count = 1; | |
46f18e3a | 685 | if (pdomain->coord_type == DOMAIN_COORD_TYPE_SW_ALL) |
3b2d9942 | 686 | pr->performance->shared_type = CPUFREQ_SHARED_TYPE_ALL; |
46f18e3a VP |
687 | else if (pdomain->coord_type == DOMAIN_COORD_TYPE_HW_ALL) |
688 | pr->performance->shared_type = CPUFREQ_SHARED_TYPE_HW; | |
689 | else if (pdomain->coord_type == DOMAIN_COORD_TYPE_SW_ANY) | |
3b2d9942 | 690 | pr->performance->shared_type = CPUFREQ_SHARED_TYPE_ANY; |
3b2d9942 | 691 | |
193de0c7 | 692 | for_each_possible_cpu(j) { |
3b2d9942 VP |
693 | if (i == j) |
694 | continue; | |
695 | ||
696 | match_pr = processors[j]; | |
697 | if (!match_pr) | |
698 | continue; | |
699 | ||
700 | match_pdomain = &(match_pr->performance->domain_info); | |
701 | if (match_pdomain->domain != pdomain->domain) | |
702 | continue; | |
703 | ||
704 | /* Here i and j are in the same domain */ | |
705 | ||
706 | if (match_pdomain->num_processors != count_target) { | |
707 | retval = -EINVAL; | |
708 | goto err_ret; | |
709 | } | |
710 | ||
711 | if (pdomain->coord_type != match_pdomain->coord_type) { | |
712 | retval = -EINVAL; | |
713 | goto err_ret; | |
714 | } | |
715 | ||
716 | cpu_set(j, covered_cpus); | |
717 | cpu_set(j, pr->performance->shared_cpu_map); | |
718 | count++; | |
719 | } | |
720 | ||
193de0c7 | 721 | for_each_possible_cpu(j) { |
3b2d9942 VP |
722 | if (i == j) |
723 | continue; | |
724 | ||
725 | match_pr = processors[j]; | |
726 | if (!match_pr) | |
727 | continue; | |
728 | ||
729 | match_pdomain = &(match_pr->performance->domain_info); | |
730 | if (match_pdomain->domain != pdomain->domain) | |
731 | continue; | |
732 | ||
733 | match_pr->performance->shared_type = | |
734 | pr->performance->shared_type; | |
735 | match_pr->performance->shared_cpu_map = | |
736 | pr->performance->shared_cpu_map; | |
737 | } | |
738 | } | |
739 | ||
740 | err_ret: | |
741 | if (retval) { | |
742 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Error while parsing _PSD domain information. Assuming no coordination\n")); | |
743 | } | |
744 | ||
193de0c7 | 745 | for_each_possible_cpu(i) { |
3b2d9942 VP |
746 | pr = processors[i]; |
747 | if (!pr || !pr->performance) | |
748 | continue; | |
749 | ||
750 | /* Assume no coordination on any error parsing domain info */ | |
751 | if (retval) { | |
752 | cpus_clear(pr->performance->shared_cpu_map); | |
753 | cpu_set(i, pr->performance->shared_cpu_map); | |
754 | pr->performance->shared_type = CPUFREQ_SHARED_TYPE_ALL; | |
755 | } | |
756 | pr->performance = NULL; /* Will be set for real in register */ | |
757 | } | |
758 | ||
785fcccd | 759 | mutex_unlock(&performance_mutex); |
9011bff4 | 760 | return retval; |
3b2d9942 VP |
761 | } |
762 | EXPORT_SYMBOL(acpi_processor_preregister_performance); | |
763 | ||
764 | ||
1da177e4 | 765 | int |
4be44fcd LB |
766 | acpi_processor_register_performance(struct acpi_processor_performance |
767 | *performance, unsigned int cpu) | |
1da177e4 LT |
768 | { |
769 | struct acpi_processor *pr; | |
770 | ||
1da177e4 LT |
771 | |
772 | if (!(acpi_processor_ppc_status & PPC_REGISTERED)) | |
d550d98d | 773 | return -EINVAL; |
1da177e4 | 774 | |
65c19bbd | 775 | mutex_lock(&performance_mutex); |
1da177e4 LT |
776 | |
777 | pr = processors[cpu]; | |
778 | if (!pr) { | |
65c19bbd | 779 | mutex_unlock(&performance_mutex); |
d550d98d | 780 | return -ENODEV; |
1da177e4 LT |
781 | } |
782 | ||
783 | if (pr->performance) { | |
65c19bbd | 784 | mutex_unlock(&performance_mutex); |
d550d98d | 785 | return -EBUSY; |
1da177e4 LT |
786 | } |
787 | ||
a913f507 AM |
788 | WARN_ON(!performance); |
789 | ||
1da177e4 LT |
790 | pr->performance = performance; |
791 | ||
792 | if (acpi_processor_get_performance_info(pr)) { | |
793 | pr->performance = NULL; | |
65c19bbd | 794 | mutex_unlock(&performance_mutex); |
d550d98d | 795 | return -EIO; |
1da177e4 LT |
796 | } |
797 | ||
798 | acpi_cpufreq_add_file(pr); | |
799 | ||
65c19bbd | 800 | mutex_unlock(&performance_mutex); |
d550d98d | 801 | return 0; |
1da177e4 | 802 | } |
1da177e4 | 803 | |
4be44fcd | 804 | EXPORT_SYMBOL(acpi_processor_register_performance); |
1da177e4 LT |
805 | |
806 | void | |
4be44fcd LB |
807 | acpi_processor_unregister_performance(struct acpi_processor_performance |
808 | *performance, unsigned int cpu) | |
1da177e4 LT |
809 | { |
810 | struct acpi_processor *pr; | |
811 | ||
1da177e4 | 812 | |
65c19bbd | 813 | mutex_lock(&performance_mutex); |
1da177e4 LT |
814 | |
815 | pr = processors[cpu]; | |
816 | if (!pr) { | |
65c19bbd | 817 | mutex_unlock(&performance_mutex); |
d550d98d | 818 | return; |
1da177e4 LT |
819 | } |
820 | ||
a913f507 AM |
821 | if (pr->performance) |
822 | kfree(pr->performance->states); | |
1da177e4 LT |
823 | pr->performance = NULL; |
824 | ||
825 | acpi_cpufreq_remove_file(pr); | |
826 | ||
65c19bbd | 827 | mutex_unlock(&performance_mutex); |
1da177e4 | 828 | |
d550d98d | 829 | return; |
1da177e4 | 830 | } |
4be44fcd | 831 | |
1da177e4 | 832 | EXPORT_SYMBOL(acpi_processor_unregister_performance); |