]>
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" | |
1da177e4 LT |
47 | #define ACPI_PROCESSOR_FILE_PERFORMANCE "performance" |
48 | #define _COMPONENT ACPI_PROCESSOR_COMPONENT | |
f52fd66d | 49 | ACPI_MODULE_NAME("processor_perflib"); |
1da177e4 | 50 | |
65c19bbd | 51 | static DEFINE_MUTEX(performance_mutex); |
1da177e4 LT |
52 | |
53 | /* | |
54 | * _PPC support is implemented as a CPUfreq policy notifier: | |
55 | * This means each time a CPUfreq driver registered also with | |
56 | * the ACPI core is asked to change the speed policy, the maximum | |
57 | * value is adjusted so that it is within the platform limit. | |
58 | * | |
59 | * Also, when a new platform limit value is detected, the CPUfreq | |
60 | * policy is adjusted accordingly. | |
61 | */ | |
62 | ||
623b78c3 TR |
63 | static unsigned int ignore_ppc = 0; |
64 | module_param(ignore_ppc, uint, 0644); | |
65 | MODULE_PARM_DESC(ignore_ppc, "If the frequency of your machine gets wrongly" \ | |
66 | "limited by BIOS, this should help"); | |
67 | ||
1da177e4 LT |
68 | #define PPC_REGISTERED 1 |
69 | #define PPC_IN_USE 2 | |
70 | ||
71 | static int acpi_processor_ppc_status = 0; | |
72 | ||
73 | static int acpi_processor_ppc_notifier(struct notifier_block *nb, | |
4be44fcd | 74 | unsigned long event, void *data) |
1da177e4 LT |
75 | { |
76 | struct cpufreq_policy *policy = data; | |
77 | struct acpi_processor *pr; | |
78 | unsigned int ppc = 0; | |
79 | ||
623b78c3 TR |
80 | if (ignore_ppc) |
81 | return 0; | |
82 | ||
65c19bbd | 83 | mutex_lock(&performance_mutex); |
1da177e4 LT |
84 | |
85 | if (event != CPUFREQ_INCOMPATIBLE) | |
86 | goto out; | |
87 | ||
88 | pr = processors[policy->cpu]; | |
89 | if (!pr || !pr->performance) | |
90 | goto out; | |
91 | ||
4be44fcd | 92 | ppc = (unsigned int)pr->performance_platform_limit; |
1da177e4 | 93 | |
0916bd3e | 94 | if (ppc >= pr->performance->state_count) |
1da177e4 LT |
95 | goto out; |
96 | ||
97 | cpufreq_verify_within_limits(policy, 0, | |
4be44fcd LB |
98 | pr->performance->states[ppc]. |
99 | core_frequency * 1000); | |
1da177e4 | 100 | |
4be44fcd | 101 | out: |
65c19bbd | 102 | mutex_unlock(&performance_mutex); |
1da177e4 LT |
103 | |
104 | return 0; | |
105 | } | |
106 | ||
1da177e4 LT |
107 | static struct notifier_block acpi_ppc_notifier_block = { |
108 | .notifier_call = acpi_processor_ppc_notifier, | |
109 | }; | |
110 | ||
4be44fcd | 111 | static int acpi_processor_get_platform_limit(struct acpi_processor *pr) |
1da177e4 | 112 | { |
4be44fcd LB |
113 | acpi_status status = 0; |
114 | unsigned long ppc = 0; | |
1da177e4 | 115 | |
1da177e4 LT |
116 | |
117 | if (!pr) | |
d550d98d | 118 | return -EINVAL; |
1da177e4 LT |
119 | |
120 | /* | |
121 | * _PPC indicates the maximum state currently supported by the platform | |
122 | * (e.g. 0 = states 0..n; 1 = states 1..n; etc. | |
123 | */ | |
124 | status = acpi_evaluate_integer(pr->handle, "_PPC", NULL, &ppc); | |
125 | ||
126 | if (status != AE_NOT_FOUND) | |
127 | acpi_processor_ppc_status |= PPC_IN_USE; | |
128 | ||
4be44fcd | 129 | if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) { |
a6fc6720 | 130 | ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PPC")); |
d550d98d | 131 | return -ENODEV; |
1da177e4 LT |
132 | } |
133 | ||
4be44fcd | 134 | pr->performance_platform_limit = (int)ppc; |
1da177e4 | 135 | |
d550d98d | 136 | return 0; |
1da177e4 LT |
137 | } |
138 | ||
4be44fcd | 139 | int acpi_processor_ppc_has_changed(struct acpi_processor *pr) |
1da177e4 | 140 | { |
623b78c3 TR |
141 | int ret; |
142 | ||
143 | if (ignore_ppc) | |
144 | return 0; | |
145 | ||
146 | ret = acpi_processor_get_platform_limit(pr); | |
147 | ||
1da177e4 LT |
148 | if (ret < 0) |
149 | return (ret); | |
150 | else | |
151 | return cpufreq_update_policy(pr->id); | |
152 | } | |
153 | ||
4be44fcd LB |
154 | void acpi_processor_ppc_init(void) |
155 | { | |
156 | if (!cpufreq_register_notifier | |
157 | (&acpi_ppc_notifier_block, CPUFREQ_POLICY_NOTIFIER)) | |
1da177e4 LT |
158 | acpi_processor_ppc_status |= PPC_REGISTERED; |
159 | else | |
4be44fcd LB |
160 | printk(KERN_DEBUG |
161 | "Warning: Processor Platform Limit not supported.\n"); | |
1da177e4 LT |
162 | } |
163 | ||
4be44fcd LB |
164 | void acpi_processor_ppc_exit(void) |
165 | { | |
1da177e4 | 166 | if (acpi_processor_ppc_status & PPC_REGISTERED) |
4be44fcd LB |
167 | cpufreq_unregister_notifier(&acpi_ppc_notifier_block, |
168 | CPUFREQ_POLICY_NOTIFIER); | |
1da177e4 LT |
169 | |
170 | acpi_processor_ppc_status &= ~PPC_REGISTERED; | |
171 | } | |
172 | ||
4be44fcd | 173 | static int acpi_processor_get_performance_control(struct acpi_processor *pr) |
1da177e4 | 174 | { |
4be44fcd LB |
175 | int result = 0; |
176 | acpi_status status = 0; | |
177 | struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; | |
178 | union acpi_object *pct = NULL; | |
179 | union acpi_object obj = { 0 }; | |
1da177e4 | 180 | |
1da177e4 LT |
181 | |
182 | status = acpi_evaluate_object(pr->handle, "_PCT", NULL, &buffer); | |
4be44fcd | 183 | if (ACPI_FAILURE(status)) { |
a6fc6720 | 184 | ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PCT")); |
d550d98d | 185 | return -ENODEV; |
1da177e4 LT |
186 | } |
187 | ||
4be44fcd | 188 | pct = (union acpi_object *)buffer.pointer; |
1da177e4 | 189 | if (!pct || (pct->type != ACPI_TYPE_PACKAGE) |
4be44fcd | 190 | || (pct->package.count != 2)) { |
6468463a | 191 | printk(KERN_ERR PREFIX "Invalid _PCT data\n"); |
1da177e4 LT |
192 | result = -EFAULT; |
193 | goto end; | |
194 | } | |
195 | ||
196 | /* | |
197 | * control_register | |
198 | */ | |
199 | ||
200 | obj = pct->package.elements[0]; | |
201 | ||
202 | if ((obj.type != ACPI_TYPE_BUFFER) | |
4be44fcd LB |
203 | || (obj.buffer.length < sizeof(struct acpi_pct_register)) |
204 | || (obj.buffer.pointer == NULL)) { | |
6468463a | 205 | printk(KERN_ERR PREFIX "Invalid _PCT data (control_register)\n"); |
1da177e4 LT |
206 | result = -EFAULT; |
207 | goto end; | |
208 | } | |
4be44fcd LB |
209 | memcpy(&pr->performance->control_register, obj.buffer.pointer, |
210 | sizeof(struct acpi_pct_register)); | |
1da177e4 LT |
211 | |
212 | /* | |
213 | * status_register | |
214 | */ | |
215 | ||
216 | obj = pct->package.elements[1]; | |
217 | ||
218 | if ((obj.type != ACPI_TYPE_BUFFER) | |
4be44fcd LB |
219 | || (obj.buffer.length < sizeof(struct acpi_pct_register)) |
220 | || (obj.buffer.pointer == NULL)) { | |
6468463a | 221 | printk(KERN_ERR PREFIX "Invalid _PCT data (status_register)\n"); |
1da177e4 LT |
222 | result = -EFAULT; |
223 | goto end; | |
224 | } | |
225 | ||
4be44fcd LB |
226 | memcpy(&pr->performance->status_register, obj.buffer.pointer, |
227 | sizeof(struct acpi_pct_register)); | |
1da177e4 | 228 | |
4be44fcd | 229 | end: |
02438d87 | 230 | kfree(buffer.pointer); |
1da177e4 | 231 | |
d550d98d | 232 | return result; |
1da177e4 LT |
233 | } |
234 | ||
4be44fcd | 235 | static int acpi_processor_get_performance_states(struct acpi_processor *pr) |
1da177e4 | 236 | { |
4be44fcd LB |
237 | int result = 0; |
238 | acpi_status status = AE_OK; | |
239 | struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; | |
240 | struct acpi_buffer format = { sizeof("NNNNNN"), "NNNNNN" }; | |
241 | struct acpi_buffer state = { 0, NULL }; | |
242 | union acpi_object *pss = NULL; | |
243 | int i; | |
1da177e4 | 244 | |
1da177e4 LT |
245 | |
246 | status = acpi_evaluate_object(pr->handle, "_PSS", NULL, &buffer); | |
4be44fcd | 247 | if (ACPI_FAILURE(status)) { |
a6fc6720 | 248 | ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PSS")); |
d550d98d | 249 | return -ENODEV; |
1da177e4 LT |
250 | } |
251 | ||
50dd0969 | 252 | pss = buffer.pointer; |
1da177e4 | 253 | if (!pss || (pss->type != ACPI_TYPE_PACKAGE)) { |
6468463a | 254 | printk(KERN_ERR PREFIX "Invalid _PSS data\n"); |
1da177e4 LT |
255 | result = -EFAULT; |
256 | goto end; | |
257 | } | |
258 | ||
259 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Found %d performance states\n", | |
4be44fcd | 260 | pss->package.count)); |
1da177e4 LT |
261 | |
262 | pr->performance->state_count = pss->package.count; | |
4be44fcd LB |
263 | pr->performance->states = |
264 | kmalloc(sizeof(struct acpi_processor_px) * pss->package.count, | |
265 | GFP_KERNEL); | |
1da177e4 LT |
266 | if (!pr->performance->states) { |
267 | result = -ENOMEM; | |
268 | goto end; | |
269 | } | |
270 | ||
271 | for (i = 0; i < pr->performance->state_count; i++) { | |
272 | ||
273 | struct acpi_processor_px *px = &(pr->performance->states[i]); | |
274 | ||
275 | state.length = sizeof(struct acpi_processor_px); | |
276 | state.pointer = px; | |
277 | ||
278 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Extracting state %d\n", i)); | |
279 | ||
280 | status = acpi_extract_package(&(pss->package.elements[i]), | |
4be44fcd | 281 | &format, &state); |
1da177e4 | 282 | if (ACPI_FAILURE(status)) { |
a6fc6720 | 283 | ACPI_EXCEPTION((AE_INFO, status, "Invalid _PSS data")); |
1da177e4 LT |
284 | result = -EFAULT; |
285 | kfree(pr->performance->states); | |
286 | goto end; | |
287 | } | |
288 | ||
289 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, | |
4be44fcd LB |
290 | "State [%d]: core_frequency[%d] power[%d] transition_latency[%d] bus_master_latency[%d] control[0x%x] status[0x%x]\n", |
291 | i, | |
292 | (u32) px->core_frequency, | |
293 | (u32) px->power, | |
294 | (u32) px->transition_latency, | |
295 | (u32) px->bus_master_latency, | |
296 | (u32) px->control, (u32) px->status)); | |
1da177e4 LT |
297 | |
298 | if (!px->core_frequency) { | |
6468463a LB |
299 | printk(KERN_ERR PREFIX |
300 | "Invalid _PSS data: freq is zero\n"); | |
1da177e4 LT |
301 | result = -EFAULT; |
302 | kfree(pr->performance->states); | |
303 | goto end; | |
304 | } | |
305 | } | |
306 | ||
4be44fcd | 307 | end: |
02438d87 | 308 | kfree(buffer.pointer); |
1da177e4 | 309 | |
d550d98d | 310 | return result; |
1da177e4 LT |
311 | } |
312 | ||
4be44fcd | 313 | static int acpi_processor_get_performance_info(struct acpi_processor *pr) |
1da177e4 | 314 | { |
4be44fcd LB |
315 | int result = 0; |
316 | acpi_status status = AE_OK; | |
317 | acpi_handle handle = NULL; | |
1da177e4 | 318 | |
1da177e4 LT |
319 | |
320 | if (!pr || !pr->performance || !pr->handle) | |
d550d98d | 321 | return -EINVAL; |
1da177e4 | 322 | |
1da177e4 LT |
323 | status = acpi_get_handle(pr->handle, "_PCT", &handle); |
324 | if (ACPI_FAILURE(status)) { | |
325 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, | |
4be44fcd | 326 | "ACPI-based processor performance control unavailable\n")); |
d550d98d | 327 | return -ENODEV; |
1da177e4 LT |
328 | } |
329 | ||
330 | result = acpi_processor_get_performance_control(pr); | |
331 | if (result) | |
d550d98d | 332 | return result; |
1da177e4 LT |
333 | |
334 | result = acpi_processor_get_performance_states(pr); | |
335 | if (result) | |
d550d98d | 336 | return result; |
1da177e4 | 337 | |
d550d98d | 338 | return 0; |
1da177e4 LT |
339 | } |
340 | ||
4be44fcd LB |
341 | int acpi_processor_notify_smm(struct module *calling_module) |
342 | { | |
343 | acpi_status status; | |
344 | static int is_done = 0; | |
1da177e4 | 345 | |
1da177e4 LT |
346 | |
347 | if (!(acpi_processor_ppc_status & PPC_REGISTERED)) | |
d550d98d | 348 | return -EBUSY; |
1da177e4 LT |
349 | |
350 | if (!try_module_get(calling_module)) | |
d550d98d | 351 | return -EINVAL; |
1da177e4 LT |
352 | |
353 | /* is_done is set to negative if an error occured, | |
354 | * and to postitive if _no_ error occured, but SMM | |
355 | * was already notified. This avoids double notification | |
356 | * which might lead to unexpected results... | |
357 | */ | |
358 | if (is_done > 0) { | |
359 | module_put(calling_module); | |
d550d98d | 360 | return 0; |
4be44fcd | 361 | } else if (is_done < 0) { |
1da177e4 | 362 | module_put(calling_module); |
d550d98d | 363 | return is_done; |
1da177e4 LT |
364 | } |
365 | ||
366 | is_done = -EIO; | |
367 | ||
ad71860a | 368 | /* Can't write pstate_control to smi_command if either value is zero */ |
cee324b1 | 369 | if ((!acpi_gbl_FADT.smi_command) || (!acpi_gbl_FADT.pstate_control)) { |
ad71860a | 370 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, "No SMI port or pstate_control\n")); |
1da177e4 | 371 | module_put(calling_module); |
d550d98d | 372 | return 0; |
1da177e4 LT |
373 | } |
374 | ||
4be44fcd | 375 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, |
ad71860a | 376 | "Writing pstate_control [0x%x] to smi_command [0x%x]\n", |
cee324b1 | 377 | acpi_gbl_FADT.pstate_control, acpi_gbl_FADT.smi_command)); |
1da177e4 | 378 | |
cee324b1 AS |
379 | status = acpi_os_write_port(acpi_gbl_FADT.smi_command, |
380 | (u32) acpi_gbl_FADT.pstate_control, 8); | |
4be44fcd | 381 | if (ACPI_FAILURE(status)) { |
a6fc6720 | 382 | ACPI_EXCEPTION((AE_INFO, status, |
ad71860a | 383 | "Failed to write pstate_control [0x%x] to " |
cee324b1 AS |
384 | "smi_command [0x%x]", acpi_gbl_FADT.pstate_control, |
385 | acpi_gbl_FADT.smi_command)); | |
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 | { | |
50dd0969 | 415 | struct acpi_processor *pr = seq->private; |
4be44fcd | 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 | ||
4be44fcd | 450 | static void acpi_cpufreq_add_file(struct acpi_processor *pr) |
1da177e4 | 451 | { |
4be44fcd LB |
452 | struct proc_dir_entry *entry = NULL; |
453 | struct acpi_device *device = NULL; | |
1da177e4 | 454 | |
1da177e4 LT |
455 | |
456 | if (acpi_bus_get_device(pr->handle, &device)) | |
d550d98d | 457 | return; |
1da177e4 LT |
458 | |
459 | /* add file 'performance' [R/W] */ | |
460 | entry = create_proc_entry(ACPI_PROCESSOR_FILE_PERFORMANCE, | |
632786ce | 461 | S_IFREG | S_IRUGO, |
4be44fcd | 462 | acpi_device_dir(device)); |
a6fc6720 | 463 | if (entry){ |
1da177e4 | 464 | entry->proc_fops = &acpi_processor_perf_fops; |
1da177e4 LT |
465 | entry->data = acpi_driver_data(device); |
466 | entry->owner = THIS_MODULE; | |
467 | } | |
d550d98d | 468 | return; |
1da177e4 LT |
469 | } |
470 | ||
4be44fcd | 471 | static void acpi_cpufreq_remove_file(struct acpi_processor *pr) |
1da177e4 | 472 | { |
4be44fcd | 473 | struct acpi_device *device = NULL; |
1da177e4 | 474 | |
1da177e4 LT |
475 | |
476 | if (acpi_bus_get_device(pr->handle, &device)) | |
d550d98d | 477 | return; |
1da177e4 LT |
478 | |
479 | /* remove file 'performance' */ | |
480 | remove_proc_entry(ACPI_PROCESSOR_FILE_PERFORMANCE, | |
4be44fcd | 481 | acpi_device_dir(device)); |
1da177e4 | 482 | |
d550d98d | 483 | return; |
1da177e4 LT |
484 | } |
485 | ||
486 | #else | |
4be44fcd LB |
487 | static void acpi_cpufreq_add_file(struct acpi_processor *pr) |
488 | { | |
489 | return; | |
490 | } | |
491 | static void acpi_cpufreq_remove_file(struct acpi_processor *pr) | |
492 | { | |
493 | return; | |
494 | } | |
495 | #endif /* CONFIG_X86_ACPI_CPUFREQ_PROC_INTF */ | |
1da177e4 | 496 | |
3b2d9942 VP |
497 | static int acpi_processor_get_psd(struct acpi_processor *pr) |
498 | { | |
499 | int result = 0; | |
500 | acpi_status status = AE_OK; | |
501 | struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; | |
502 | struct acpi_buffer format = {sizeof("NNNNN"), "NNNNN"}; | |
503 | struct acpi_buffer state = {0, NULL}; | |
504 | union acpi_object *psd = NULL; | |
505 | struct acpi_psd_package *pdomain; | |
506 | ||
3b2d9942 VP |
507 | status = acpi_evaluate_object(pr->handle, "_PSD", NULL, &buffer); |
508 | if (ACPI_FAILURE(status)) { | |
9011bff4 | 509 | return -ENODEV; |
3b2d9942 VP |
510 | } |
511 | ||
50dd0969 | 512 | psd = buffer.pointer; |
3b2d9942 VP |
513 | if (!psd || (psd->type != ACPI_TYPE_PACKAGE)) { |
514 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid _PSD data\n")); | |
515 | result = -EFAULT; | |
516 | goto end; | |
517 | } | |
518 | ||
519 | if (psd->package.count != 1) { | |
520 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid _PSD data\n")); | |
521 | result = -EFAULT; | |
522 | goto end; | |
523 | } | |
524 | ||
525 | pdomain = &(pr->performance->domain_info); | |
526 | ||
527 | state.length = sizeof(struct acpi_psd_package); | |
528 | state.pointer = pdomain; | |
529 | ||
530 | status = acpi_extract_package(&(psd->package.elements[0]), | |
531 | &format, &state); | |
532 | if (ACPI_FAILURE(status)) { | |
533 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Invalid _PSD data\n")); | |
534 | result = -EFAULT; | |
535 | goto end; | |
536 | } | |
537 | ||
538 | if (pdomain->num_entries != ACPI_PSD_REV0_ENTRIES) { | |
539 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Unknown _PSD:num_entries\n")); | |
540 | result = -EFAULT; | |
541 | goto end; | |
542 | } | |
543 | ||
544 | if (pdomain->revision != ACPI_PSD_REV0_REVISION) { | |
545 | ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Unknown _PSD:revision\n")); | |
546 | result = -EFAULT; | |
547 | goto end; | |
548 | } | |
549 | ||
550 | end: | |
02438d87 | 551 | kfree(buffer.pointer); |
9011bff4 | 552 | return result; |
3b2d9942 VP |
553 | } |
554 | ||
555 | int acpi_processor_preregister_performance( | |
50109292 | 556 | struct acpi_processor_performance *performance) |
3b2d9942 VP |
557 | { |
558 | int count, count_target; | |
559 | int retval = 0; | |
560 | unsigned int i, j; | |
561 | cpumask_t covered_cpus; | |
562 | struct acpi_processor *pr; | |
563 | struct acpi_psd_package *pdomain; | |
564 | struct acpi_processor *match_pr; | |
565 | struct acpi_psd_package *match_pdomain; | |
566 | ||
785fcccd | 567 | mutex_lock(&performance_mutex); |
3b2d9942 VP |
568 | |
569 | retval = 0; | |
570 | ||
571 | /* Call _PSD for all CPUs */ | |
193de0c7 | 572 | for_each_possible_cpu(i) { |
3b2d9942 VP |
573 | pr = processors[i]; |
574 | if (!pr) { | |
575 | /* Look only at processors in ACPI namespace */ | |
576 | continue; | |
577 | } | |
578 | ||
579 | if (pr->performance) { | |
580 | retval = -EBUSY; | |
581 | continue; | |
582 | } | |
583 | ||
50109292 | 584 | if (!performance || !percpu_ptr(performance, i)) { |
3b2d9942 VP |
585 | retval = -EINVAL; |
586 | continue; | |
587 | } | |
588 | ||
50109292 | 589 | pr->performance = percpu_ptr(performance, i); |
3b2d9942 VP |
590 | cpu_set(i, pr->performance->shared_cpu_map); |
591 | if (acpi_processor_get_psd(pr)) { | |
592 | retval = -EINVAL; | |
593 | continue; | |
594 | } | |
595 | } | |
596 | if (retval) | |
597 | goto err_ret; | |
598 | ||
599 | /* | |
600 | * Now that we have _PSD data from all CPUs, lets setup P-state | |
601 | * domain info. | |
602 | */ | |
193de0c7 | 603 | for_each_possible_cpu(i) { |
3b2d9942 VP |
604 | pr = processors[i]; |
605 | if (!pr) | |
606 | continue; | |
607 | ||
608 | /* Basic validity check for domain info */ | |
609 | pdomain = &(pr->performance->domain_info); | |
610 | if ((pdomain->revision != ACPI_PSD_REV0_REVISION) || | |
611 | (pdomain->num_entries != ACPI_PSD_REV0_ENTRIES)) { | |
612 | retval = -EINVAL; | |
613 | goto err_ret; | |
614 | } | |
615 | if (pdomain->coord_type != DOMAIN_COORD_TYPE_SW_ALL && | |
616 | pdomain->coord_type != DOMAIN_COORD_TYPE_SW_ANY && | |
617 | pdomain->coord_type != DOMAIN_COORD_TYPE_HW_ALL) { | |
618 | retval = -EINVAL; | |
619 | goto err_ret; | |
620 | } | |
621 | } | |
622 | ||
623 | cpus_clear(covered_cpus); | |
193de0c7 | 624 | for_each_possible_cpu(i) { |
3b2d9942 VP |
625 | pr = processors[i]; |
626 | if (!pr) | |
627 | continue; | |
628 | ||
629 | if (cpu_isset(i, covered_cpus)) | |
630 | continue; | |
631 | ||
632 | pdomain = &(pr->performance->domain_info); | |
633 | cpu_set(i, pr->performance->shared_cpu_map); | |
634 | cpu_set(i, covered_cpus); | |
635 | if (pdomain->num_processors <= 1) | |
636 | continue; | |
637 | ||
638 | /* Validate the Domain info */ | |
639 | count_target = pdomain->num_processors; | |
640 | count = 1; | |
46f18e3a | 641 | if (pdomain->coord_type == DOMAIN_COORD_TYPE_SW_ALL) |
3b2d9942 | 642 | pr->performance->shared_type = CPUFREQ_SHARED_TYPE_ALL; |
46f18e3a VP |
643 | else if (pdomain->coord_type == DOMAIN_COORD_TYPE_HW_ALL) |
644 | pr->performance->shared_type = CPUFREQ_SHARED_TYPE_HW; | |
645 | else if (pdomain->coord_type == DOMAIN_COORD_TYPE_SW_ANY) | |
3b2d9942 | 646 | pr->performance->shared_type = CPUFREQ_SHARED_TYPE_ANY; |
3b2d9942 | 647 | |
193de0c7 | 648 | for_each_possible_cpu(j) { |
3b2d9942 VP |
649 | if (i == j) |
650 | continue; | |
651 | ||
652 | match_pr = processors[j]; | |
653 | if (!match_pr) | |
654 | continue; | |
655 | ||
656 | match_pdomain = &(match_pr->performance->domain_info); | |
657 | if (match_pdomain->domain != pdomain->domain) | |
658 | continue; | |
659 | ||
660 | /* Here i and j are in the same domain */ | |
661 | ||
662 | if (match_pdomain->num_processors != count_target) { | |
663 | retval = -EINVAL; | |
664 | goto err_ret; | |
665 | } | |
666 | ||
667 | if (pdomain->coord_type != match_pdomain->coord_type) { | |
668 | retval = -EINVAL; | |
669 | goto err_ret; | |
670 | } | |
671 | ||
672 | cpu_set(j, covered_cpus); | |
673 | cpu_set(j, pr->performance->shared_cpu_map); | |
674 | count++; | |
675 | } | |
676 | ||
193de0c7 | 677 | for_each_possible_cpu(j) { |
3b2d9942 VP |
678 | if (i == j) |
679 | continue; | |
680 | ||
681 | match_pr = processors[j]; | |
682 | if (!match_pr) | |
683 | continue; | |
684 | ||
685 | match_pdomain = &(match_pr->performance->domain_info); | |
686 | if (match_pdomain->domain != pdomain->domain) | |
687 | continue; | |
688 | ||
689 | match_pr->performance->shared_type = | |
690 | pr->performance->shared_type; | |
691 | match_pr->performance->shared_cpu_map = | |
692 | pr->performance->shared_cpu_map; | |
693 | } | |
694 | } | |
695 | ||
696 | err_ret: | |
193de0c7 | 697 | for_each_possible_cpu(i) { |
3b2d9942 VP |
698 | pr = processors[i]; |
699 | if (!pr || !pr->performance) | |
700 | continue; | |
701 | ||
702 | /* Assume no coordination on any error parsing domain info */ | |
703 | if (retval) { | |
704 | cpus_clear(pr->performance->shared_cpu_map); | |
705 | cpu_set(i, pr->performance->shared_cpu_map); | |
706 | pr->performance->shared_type = CPUFREQ_SHARED_TYPE_ALL; | |
707 | } | |
708 | pr->performance = NULL; /* Will be set for real in register */ | |
709 | } | |
710 | ||
785fcccd | 711 | mutex_unlock(&performance_mutex); |
9011bff4 | 712 | return retval; |
3b2d9942 VP |
713 | } |
714 | EXPORT_SYMBOL(acpi_processor_preregister_performance); | |
715 | ||
716 | ||
1da177e4 | 717 | int |
4be44fcd LB |
718 | acpi_processor_register_performance(struct acpi_processor_performance |
719 | *performance, unsigned int cpu) | |
1da177e4 LT |
720 | { |
721 | struct acpi_processor *pr; | |
722 | ||
1da177e4 LT |
723 | |
724 | if (!(acpi_processor_ppc_status & PPC_REGISTERED)) | |
d550d98d | 725 | return -EINVAL; |
1da177e4 | 726 | |
65c19bbd | 727 | mutex_lock(&performance_mutex); |
1da177e4 LT |
728 | |
729 | pr = processors[cpu]; | |
730 | if (!pr) { | |
65c19bbd | 731 | mutex_unlock(&performance_mutex); |
d550d98d | 732 | return -ENODEV; |
1da177e4 LT |
733 | } |
734 | ||
735 | if (pr->performance) { | |
65c19bbd | 736 | mutex_unlock(&performance_mutex); |
d550d98d | 737 | return -EBUSY; |
1da177e4 LT |
738 | } |
739 | ||
a913f507 AM |
740 | WARN_ON(!performance); |
741 | ||
1da177e4 LT |
742 | pr->performance = performance; |
743 | ||
744 | if (acpi_processor_get_performance_info(pr)) { | |
745 | pr->performance = NULL; | |
65c19bbd | 746 | mutex_unlock(&performance_mutex); |
d550d98d | 747 | return -EIO; |
1da177e4 LT |
748 | } |
749 | ||
750 | acpi_cpufreq_add_file(pr); | |
751 | ||
65c19bbd | 752 | mutex_unlock(&performance_mutex); |
d550d98d | 753 | return 0; |
1da177e4 | 754 | } |
1da177e4 | 755 | |
4be44fcd | 756 | EXPORT_SYMBOL(acpi_processor_register_performance); |
1da177e4 LT |
757 | |
758 | void | |
4be44fcd LB |
759 | acpi_processor_unregister_performance(struct acpi_processor_performance |
760 | *performance, unsigned int cpu) | |
1da177e4 LT |
761 | { |
762 | struct acpi_processor *pr; | |
763 | ||
1da177e4 | 764 | |
65c19bbd | 765 | mutex_lock(&performance_mutex); |
1da177e4 LT |
766 | |
767 | pr = processors[cpu]; | |
768 | if (!pr) { | |
65c19bbd | 769 | mutex_unlock(&performance_mutex); |
d550d98d | 770 | return; |
1da177e4 LT |
771 | } |
772 | ||
a913f507 AM |
773 | if (pr->performance) |
774 | kfree(pr->performance->states); | |
1da177e4 LT |
775 | pr->performance = NULL; |
776 | ||
777 | acpi_cpufreq_remove_file(pr); | |
778 | ||
65c19bbd | 779 | mutex_unlock(&performance_mutex); |
1da177e4 | 780 | |
d550d98d | 781 | return; |
1da177e4 | 782 | } |
4be44fcd | 783 | |
1da177e4 | 784 | EXPORT_SYMBOL(acpi_processor_unregister_performance); |