1 | /* $NetBSD: acpi_power.c,v 1.34 2014/02/25 18:30:09 pooka Exp $ */ |
2 | |
3 | /*- |
4 | * Copyright (c) 2009, 2010, 2011 The NetBSD Foundation, Inc. |
5 | * All rights reserved. |
6 | * |
7 | * This code is derived from software contributed to The NetBSD Foundation |
8 | * by Jukka Ruohonen. |
9 | * |
10 | * Redistribution and use in source and binary forms, with or without |
11 | * modification, are permitted provided that the following conditions |
12 | * are met: |
13 | * 1. Redistributions of source code must retain the above copyright |
14 | * notice, this list of conditions and the following disclaimer. |
15 | * 2. Redistributions in binary form must reproduce the above copyright |
16 | * notice, this list of conditions and the following disclaimer in the |
17 | * documentation and/or other materials provided with the distribution. |
18 | * |
19 | * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS |
20 | * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
21 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
22 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS |
23 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
29 | * POSSIBILITY OF SUCH DAMAGE. |
30 | */ |
31 | |
32 | /*- |
33 | * Copyright (c) 2001 Michael Smith |
34 | * All rights reserved. |
35 | * |
36 | * Redistribution and use in source and binary forms, with or without |
37 | * modification, are permitted provided that the following conditions |
38 | * are met: |
39 | * 1. Redistributions of source code must retain the above copyright |
40 | * notice, this list of conditions and the following disclaimer. |
41 | * 2. Redistributions in binary form must reproduce the above copyright |
42 | * notice, this list of conditions and the following disclaimer in the |
43 | * documentation and/or other materials provided with the distribution. |
44 | * |
45 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
46 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
47 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
48 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
49 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
50 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
51 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
52 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
53 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
54 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
55 | * SUCH DAMAGE. |
56 | */ |
57 | |
58 | #include <sys/cdefs.h> |
59 | __KERNEL_RCSID(0, "$NetBSD: acpi_power.c,v 1.34 2014/02/25 18:30:09 pooka Exp $" ); |
60 | |
61 | #include <sys/param.h> |
62 | #include <sys/kmem.h> |
63 | #include <sys/mutex.h> |
64 | #include <sys/sysctl.h> |
65 | |
66 | #include <dev/acpi/acpireg.h> |
67 | #include <dev/acpi/acpivar.h> |
68 | #include <dev/acpi/acpi_pci.h> |
69 | #include <dev/acpi/acpi_power.h> |
70 | |
71 | #define _COMPONENT ACPI_BUS_COMPONENT |
72 | ACPI_MODULE_NAME ("acpi_power" ) |
73 | |
74 | #define ACPI_STA_POW_OFF 0x00 |
75 | #define ACPI_STA_POW_ON 0x01 |
76 | |
77 | struct acpi_power_res { |
78 | ACPI_HANDLE res_handle; |
79 | ACPI_INTEGER res_level; |
80 | ACPI_INTEGER res_order; |
81 | ACPI_HANDLE res_ref[5]; |
82 | char res_name[5]; |
83 | kmutex_t res_mutex; |
84 | |
85 | TAILQ_ENTRY(acpi_power_res) res_list; |
86 | }; |
87 | |
88 | static TAILQ_HEAD(, acpi_power_res) res_head = |
89 | TAILQ_HEAD_INITIALIZER(res_head); |
90 | |
91 | static int32_t acpi_power_acpinode = CTL_EOL; |
92 | static int32_t acpi_power_powernode = CTL_EOL; |
93 | |
94 | static struct acpi_power_res *acpi_power_res_init(ACPI_HANDLE); |
95 | static struct acpi_power_res *acpi_power_res_get(ACPI_HANDLE); |
96 | |
97 | static ACPI_STATUS acpi_power_get_direct(struct acpi_devnode *); |
98 | static ACPI_STATUS acpi_power_get_indirect(struct acpi_devnode *); |
99 | static ACPI_STATUS acpi_power_switch(struct acpi_devnode *, |
100 | int, bool); |
101 | static ACPI_STATUS acpi_power_res_ref(struct acpi_power_res *, |
102 | ACPI_HANDLE); |
103 | static ACPI_STATUS acpi_power_res_deref(struct acpi_power_res *, |
104 | ACPI_HANDLE); |
105 | static ACPI_STATUS acpi_power_res_sta(ACPI_OBJECT *, void *); |
106 | |
107 | static ACPI_OBJECT *acpi_power_pkg_get(ACPI_HANDLE, int); |
108 | static int acpi_power_sysctl(SYSCTLFN_PROTO); |
109 | static const char *acpi_xname(ACPI_HANDLE); |
110 | |
111 | static struct acpi_power_res * |
112 | acpi_power_res_init(ACPI_HANDLE hdl) |
113 | { |
114 | struct acpi_power_res *tmp = NULL; |
115 | struct acpi_power_res *res = NULL; |
116 | ACPI_OBJECT *obj; |
117 | ACPI_BUFFER buf; |
118 | ACPI_STATUS rv; |
119 | size_t i; |
120 | |
121 | rv = acpi_eval_struct(hdl, NULL, &buf); |
122 | |
123 | if (ACPI_FAILURE(rv)) |
124 | goto out; |
125 | |
126 | obj = buf.Pointer; |
127 | |
128 | if (obj->Type != ACPI_TYPE_POWER) { |
129 | rv = AE_TYPE; |
130 | goto out; |
131 | } |
132 | |
133 | res = kmem_zalloc(sizeof(*res), KM_SLEEP); |
134 | |
135 | if (res == NULL) { |
136 | rv = AE_NO_MEMORY; |
137 | goto out; |
138 | } |
139 | |
140 | res->res_handle = hdl; |
141 | res->res_level = obj->PowerResource.SystemLevel; |
142 | res->res_order = obj->PowerResource.ResourceOrder; |
143 | |
144 | (void)strlcpy(res->res_name, |
145 | acpi_xname(hdl), sizeof(res->res_name)); |
146 | |
147 | for (i = 0; i < __arraycount(res->res_ref); i++) |
148 | res->res_ref[i] = NULL; |
149 | |
150 | mutex_init(&res->res_mutex, MUTEX_DEFAULT, IPL_NONE); |
151 | |
152 | /* |
153 | * Power resources should be ordered. |
154 | * |
155 | * These *should* be enabled from low values to high |
156 | * values and disabled from high values to low values. |
157 | */ |
158 | TAILQ_FOREACH(tmp, &res_head, res_list) { |
159 | |
160 | if (res->res_order < tmp->res_order) { |
161 | TAILQ_INSERT_BEFORE(tmp, res, res_list); |
162 | break; |
163 | } |
164 | } |
165 | |
166 | if (tmp == NULL) |
167 | TAILQ_INSERT_TAIL(&res_head, res, res_list); |
168 | |
169 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, "%s added to the " |
170 | "power resource queue\n" , res->res_name)); |
171 | |
172 | out: |
173 | if (buf.Pointer != NULL) |
174 | ACPI_FREE(buf.Pointer); |
175 | |
176 | return res; |
177 | } |
178 | |
179 | static struct acpi_power_res * |
180 | acpi_power_res_get(ACPI_HANDLE hdl) |
181 | { |
182 | struct acpi_power_res *res; |
183 | |
184 | TAILQ_FOREACH(res, &res_head, res_list) { |
185 | |
186 | if (res->res_handle == hdl) |
187 | return res; |
188 | } |
189 | |
190 | return acpi_power_res_init(hdl); |
191 | } |
192 | |
193 | bool |
194 | acpi_power_register(ACPI_HANDLE hdl) |
195 | { |
196 | return true; |
197 | } |
198 | |
199 | void |
200 | acpi_power_deregister(ACPI_HANDLE hdl) |
201 | { |
202 | struct acpi_devnode *ad = acpi_match_node(hdl); |
203 | struct acpi_power_res *res; |
204 | |
205 | if (ad == NULL) |
206 | return; |
207 | |
208 | /* |
209 | * Remove all references in each resource. |
210 | */ |
211 | TAILQ_FOREACH(res, &res_head, res_list) |
212 | (void)acpi_power_res_deref(res, ad->ad_handle); |
213 | } |
214 | |
215 | /* |
216 | * Get the D-state of an ACPI device node. |
217 | */ |
218 | bool |
219 | acpi_power_get(ACPI_HANDLE hdl, int *state) |
220 | { |
221 | struct acpi_devnode *ad = acpi_match_node(hdl); |
222 | ACPI_STATUS rv; |
223 | |
224 | if (ad == NULL) |
225 | return false; |
226 | |
227 | /* |
228 | * As _PSC may be broken, first try to |
229 | * retrieve the power state indirectly |
230 | * via power resources. |
231 | */ |
232 | rv = acpi_power_get_indirect(ad); |
233 | |
234 | if (ACPI_FAILURE(rv)) |
235 | rv = acpi_power_get_direct(ad); |
236 | |
237 | if (ACPI_FAILURE(rv)) |
238 | goto fail; |
239 | |
240 | KASSERT(ad->ad_state != ACPI_STATE_ERROR); |
241 | |
242 | if (ad->ad_state < ACPI_STATE_D0 || ad->ad_state > ACPI_STATE_D3) { |
243 | rv = AE_BAD_VALUE; |
244 | goto fail; |
245 | } |
246 | |
247 | if (state != NULL) |
248 | *state = ad->ad_state; |
249 | |
250 | return true; |
251 | |
252 | fail: |
253 | ad->ad_state = ACPI_STATE_ERROR; |
254 | |
255 | if (state != NULL) |
256 | *state = ad->ad_state; |
257 | |
258 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, "failed to get power state " |
259 | "for %s: %s\n" , ad->ad_name, AcpiFormatException(rv))); |
260 | |
261 | return false; |
262 | } |
263 | |
264 | static ACPI_STATUS |
265 | acpi_power_get_direct(struct acpi_devnode *ad) |
266 | { |
267 | ACPI_INTEGER val = 0; |
268 | ACPI_STATUS rv; |
269 | |
270 | rv = acpi_eval_integer(ad->ad_handle, "_PSC" , &val); |
271 | |
272 | KDASSERT((uint64_t)val < INT_MAX); |
273 | |
274 | ad->ad_state = (int)val; |
275 | |
276 | return rv; |
277 | } |
278 | |
279 | static ACPI_STATUS |
280 | acpi_power_get_indirect(struct acpi_devnode *ad) |
281 | { |
282 | ACPI_OBJECT *pkg; |
283 | ACPI_STATUS rv; |
284 | int i; |
285 | |
286 | CTASSERT(ACPI_STATE_D0 == 0 && ACPI_STATE_D1 == 1); |
287 | CTASSERT(ACPI_STATE_D2 == 2 && ACPI_STATE_D3 == 3); |
288 | |
289 | /* |
290 | * The device is in a given D-state if all resources are on. |
291 | * To derive this, evaluate all elements in each _PRx package |
292 | * (x = 0 ... 3) and break if the noted condition becomes true. |
293 | */ |
294 | for (ad->ad_state = ACPI_STATE_D3, i = 0; i < ACPI_STATE_D3; i++) { |
295 | |
296 | pkg = acpi_power_pkg_get(ad->ad_handle, i); |
297 | |
298 | if (pkg == NULL) |
299 | continue; |
300 | |
301 | /* |
302 | * For each element in the _PRx package, evaluate _STA |
303 | * and return AE_OK only if all power resources are on. |
304 | */ |
305 | rv = acpi_foreach_package_object(pkg, acpi_power_res_sta, ad); |
306 | |
307 | if (ACPI_FAILURE(rv) && rv != AE_CTRL_FALSE) |
308 | goto out; |
309 | |
310 | if (ACPI_SUCCESS(rv)) { |
311 | ad->ad_state = i; |
312 | goto out; |
313 | } |
314 | |
315 | ACPI_FREE(pkg); pkg = NULL; |
316 | } |
317 | |
318 | KASSERT(ad->ad_state == ACPI_STATE_D3); |
319 | |
320 | return AE_OK; |
321 | |
322 | out: |
323 | ACPI_FREE(pkg); |
324 | |
325 | return rv; |
326 | } |
327 | |
328 | /* |
329 | * Set the D-state of an ACPI device node. |
330 | */ |
331 | bool |
332 | acpi_power_set(ACPI_HANDLE hdl, int state) |
333 | { |
334 | struct acpi_devnode *ad = acpi_match_node(hdl); |
335 | ACPI_STATUS rv; |
336 | char path[5]; |
337 | int old; |
338 | |
339 | if (ad == NULL) |
340 | return false; |
341 | |
342 | if (state < ACPI_STATE_D0 || state > ACPI_STATE_D3) { |
343 | rv = AE_BAD_PARAMETER; |
344 | goto fail; |
345 | } |
346 | |
347 | if (acpi_power_get(ad->ad_handle, &old) != true) { |
348 | rv = AE_NOT_FOUND; |
349 | goto fail; |
350 | } |
351 | |
352 | KASSERT(ad->ad_state == old); |
353 | KASSERT(ad->ad_state != ACPI_STATE_ERROR); |
354 | |
355 | if (ad->ad_state == state) { |
356 | rv = AE_ALREADY_EXISTS; |
357 | goto fail; |
358 | } |
359 | |
360 | /* |
361 | * It is only possible to go to D0 ("on") from D3 ("off"). |
362 | */ |
363 | if (ad->ad_state == ACPI_STATE_D3 && state != ACPI_STATE_D0) { |
364 | rv = AE_BAD_PARAMETER; |
365 | goto fail; |
366 | } |
367 | |
368 | /* |
369 | * As noted in ACPI 4.0 (appendix A.2.1), the bus power state |
370 | * should never be lower than the highest state of one of its |
371 | * devices. Consequently, we cannot set the state to a lower |
372 | * (i.e. higher power) state than the parent device's state. |
373 | */ |
374 | if ((ad->ad_parent != NULL) && |
375 | (ad->ad_parent->ad_flags & ACPI_DEVICE_POWER) != 0) { |
376 | |
377 | if (ad->ad_parent->ad_state > state) { |
378 | rv = AE_ABORT_METHOD; |
379 | goto fail; |
380 | } |
381 | } |
382 | |
383 | /* |
384 | * We first sweep through the resources required for the target |
385 | * state, turning things on and building references. After this |
386 | * we dereference the resources required for the current state, |
387 | * turning the resources off as we go. |
388 | */ |
389 | rv = acpi_power_switch(ad, state, true); |
390 | |
391 | if (ACPI_FAILURE(rv) && rv != AE_CTRL_CONTINUE) |
392 | goto fail; |
393 | |
394 | rv = acpi_power_switch(ad, ad->ad_state, false); |
395 | |
396 | if (ACPI_FAILURE(rv) && rv != AE_CTRL_CONTINUE) |
397 | goto fail; |
398 | |
399 | /* |
400 | * Last but not least, invoke the power state switch method, |
401 | * if available. Because some systems use only _PSx for the |
402 | * power state transitions, we do this even if there is no _PRx. |
403 | */ |
404 | (void)snprintf(path, sizeof(path), "_PS%d" , state); |
405 | (void)AcpiEvaluateObject(ad->ad_handle, path, NULL, NULL); |
406 | |
407 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, "%s turned from " |
408 | "D%d to D%d\n" , ad->ad_name, old, state)); |
409 | |
410 | ad->ad_state = state; |
411 | |
412 | return true; |
413 | |
414 | fail: |
415 | ad->ad_state = ACPI_STATE_ERROR; |
416 | |
417 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, "failed to set power state to D%d " |
418 | "for %s: %s\n" , state, ad->ad_name, AcpiFormatException(rv))); |
419 | |
420 | return false; |
421 | } |
422 | |
423 | static ACPI_STATUS |
424 | acpi_power_switch(struct acpi_devnode *ad, int state, bool on) |
425 | { |
426 | ACPI_OBJECT *elm, *pkg; |
427 | ACPI_STATUS rv = AE_OK; |
428 | ACPI_HANDLE hdl; |
429 | uint32_t i, n; |
430 | |
431 | /* |
432 | * For each element in the _PRx package, fetch |
433 | * the reference handle, search for this handle |
434 | * from the power resource queue, and turn the |
435 | * resource behind the handle on or off. |
436 | */ |
437 | pkg = acpi_power_pkg_get(ad->ad_handle, state); |
438 | |
439 | if (pkg == NULL) |
440 | return AE_CTRL_CONTINUE; |
441 | |
442 | n = pkg->Package.Count; |
443 | |
444 | for (i = 0; i < n; i++) { |
445 | |
446 | elm = &pkg->Package.Elements[i]; |
447 | rv = acpi_eval_reference_handle(elm, &hdl); |
448 | |
449 | if (ACPI_FAILURE(rv)) |
450 | continue; |
451 | |
452 | (void)acpi_power_res(hdl, ad->ad_handle, on); |
453 | } |
454 | |
455 | ACPI_FREE(pkg); |
456 | |
457 | return rv; |
458 | } |
459 | |
460 | ACPI_STATUS |
461 | acpi_power_res(ACPI_HANDLE hdl, ACPI_HANDLE ref, bool on) |
462 | { |
463 | struct acpi_power_res *res; |
464 | const char *str; |
465 | ACPI_STATUS rv; |
466 | |
467 | /* |
468 | * Search for the resource. |
469 | */ |
470 | res = acpi_power_res_get(hdl); |
471 | |
472 | if (res == NULL) |
473 | return AE_NOT_FOUND; |
474 | |
475 | if (ref == NULL) |
476 | return AE_BAD_PARAMETER; |
477 | |
478 | /* |
479 | * Adjust the reference counting. This is |
480 | * necessary since a single power resource |
481 | * can be shared by multiple devices. |
482 | */ |
483 | if (on) { |
484 | rv = acpi_power_res_ref(res, ref); |
485 | str = "_ON" ; |
486 | } else { |
487 | rv = acpi_power_res_deref(res, ref); |
488 | str = "_OFF" ; |
489 | } |
490 | |
491 | if (ACPI_FAILURE(rv)) |
492 | return rv; |
493 | |
494 | /* |
495 | * Turn the resource on or off. |
496 | */ |
497 | return AcpiEvaluateObject(res->res_handle, str, NULL, NULL); |
498 | } |
499 | |
500 | static ACPI_STATUS |
501 | acpi_power_res_ref(struct acpi_power_res *res, ACPI_HANDLE ref) |
502 | { |
503 | size_t i, j = SIZE_MAX; |
504 | |
505 | mutex_enter(&res->res_mutex); |
506 | |
507 | for (i = 0; i < __arraycount(res->res_ref); i++) { |
508 | |
509 | /* |
510 | * Do not error out if the handle |
511 | * has already been referenced. |
512 | */ |
513 | if (res->res_ref[i] == ref) { |
514 | mutex_exit(&res->res_mutex); |
515 | return AE_OK; |
516 | } |
517 | |
518 | if (j == SIZE_MAX && res->res_ref[i] == NULL) |
519 | j = i; |
520 | } |
521 | |
522 | if (j == SIZE_MAX) { |
523 | mutex_exit(&res->res_mutex); |
524 | return AE_LIMIT; |
525 | } |
526 | |
527 | res->res_ref[j] = ref; |
528 | mutex_exit(&res->res_mutex); |
529 | |
530 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, "%s referenced " |
531 | "by %s\n" , res->res_name, acpi_xname(ref))); |
532 | |
533 | return AE_OK; |
534 | } |
535 | |
536 | static ACPI_STATUS |
537 | acpi_power_res_deref(struct acpi_power_res *res, ACPI_HANDLE ref) |
538 | { |
539 | size_t i; |
540 | |
541 | mutex_enter(&res->res_mutex); |
542 | |
543 | for (i = 0; i < __arraycount(res->res_ref); i++) { |
544 | |
545 | if (res->res_ref[i] != ref) |
546 | continue; |
547 | |
548 | res->res_ref[i] = NULL; |
549 | mutex_exit(&res->res_mutex); |
550 | |
551 | ACPI_DEBUG_PRINT((ACPI_DB_INFO, "%s dereferenced " |
552 | "by %s\n" , res->res_name, acpi_xname(ref))); |
553 | |
554 | return AE_OK; |
555 | } |
556 | |
557 | /* |
558 | * If the array remains to be non-empty, |
559 | * something else is using the resource |
560 | * and hence it can not be turned off. |
561 | */ |
562 | mutex_exit(&res->res_mutex); |
563 | |
564 | return AE_ABORT_METHOD; |
565 | } |
566 | |
567 | static ACPI_STATUS |
568 | acpi_power_res_sta(ACPI_OBJECT *elm, void *arg) |
569 | { |
570 | ACPI_INTEGER val; |
571 | ACPI_HANDLE hdl; |
572 | ACPI_STATUS rv; |
573 | |
574 | rv = acpi_eval_reference_handle(elm, &hdl); |
575 | |
576 | if (ACPI_FAILURE(rv)) |
577 | goto fail; |
578 | |
579 | rv = acpi_eval_integer(hdl, "_STA" , &val); |
580 | |
581 | if (ACPI_FAILURE(rv)) |
582 | goto fail; |
583 | |
584 | KDASSERT((uint64_t)val < INT_MAX); |
585 | |
586 | if ((int)val != ACPI_STA_POW_ON && (int)val != ACPI_STA_POW_OFF) |
587 | return AE_BAD_VALUE; |
588 | |
589 | if ((int)val != ACPI_STA_POW_ON) |
590 | return AE_CTRL_FALSE; /* XXX: Not an error. */ |
591 | |
592 | return AE_OK; |
593 | |
594 | fail: |
595 | if (rv == AE_CTRL_FALSE) |
596 | rv = AE_ERROR; |
597 | |
598 | ACPI_DEBUG_PRINT((ACPI_DB_DEBUG_OBJECT, "failed to evaluate _STA " |
599 | "for %s: %s\n" , acpi_xname(hdl), AcpiFormatException(rv))); |
600 | |
601 | return rv; |
602 | } |
603 | |
604 | static ACPI_OBJECT * |
605 | acpi_power_pkg_get(ACPI_HANDLE hdl, int state) |
606 | { |
607 | char path[5] = "_PR?" ; |
608 | ACPI_OBJECT *obj; |
609 | ACPI_BUFFER buf; |
610 | ACPI_STATUS rv; |
611 | |
612 | path[3] = '0' + state; |
613 | |
614 | rv = acpi_eval_struct(hdl, path, &buf); |
615 | |
616 | if (ACPI_FAILURE(rv)) |
617 | goto fail; |
618 | |
619 | if (buf.Length == 0) { |
620 | rv = AE_LIMIT; |
621 | goto fail; |
622 | } |
623 | |
624 | obj = buf.Pointer; |
625 | |
626 | if (obj->Type != ACPI_TYPE_PACKAGE) { |
627 | rv = AE_TYPE; |
628 | goto fail; |
629 | } |
630 | |
631 | if (obj->Package.Count == 0) { |
632 | rv = AE_LIMIT; |
633 | goto fail; |
634 | } |
635 | |
636 | return obj; |
637 | |
638 | fail: |
639 | if (buf.Pointer != NULL) |
640 | ACPI_FREE(buf.Pointer); |
641 | |
642 | ACPI_DEBUG_PRINT((ACPI_DB_DEBUG_OBJECT, "failed to evaluate %s for " |
643 | "%s: %s\n" , path, acpi_xname(hdl), AcpiFormatException(rv))); |
644 | |
645 | return NULL; |
646 | } |
647 | |
648 | SYSCTL_SETUP(sysctl_acpi_power_setup, "sysctl hw.acpi.power subtree setup" ) |
649 | { |
650 | const struct sysctlnode *anode; |
651 | int err; |
652 | |
653 | err = sysctl_createv(NULL, 0, NULL, &anode, |
654 | CTLFLAG_PERMANENT, CTLTYPE_NODE, "acpi" , |
655 | NULL, NULL, 0, NULL, 0, |
656 | CTL_HW, CTL_CREATE, CTL_EOL); |
657 | |
658 | if (err != 0) |
659 | return; |
660 | |
661 | acpi_power_acpinode = anode->sysctl_num; |
662 | |
663 | err = sysctl_createv(NULL, 0, &anode, &anode, |
664 | CTLFLAG_PERMANENT, CTLTYPE_NODE, |
665 | "power" , SYSCTL_DESCR("ACPI device power states" ), |
666 | NULL, 0, NULL, 0, |
667 | CTL_CREATE, CTL_EOL); |
668 | |
669 | if (err != 0) |
670 | return; |
671 | |
672 | acpi_power_powernode = anode->sysctl_num; |
673 | } |
674 | |
675 | void |
676 | acpi_power_add(struct acpi_devnode *ad) |
677 | { |
678 | const char *str = NULL; |
679 | device_t dev; |
680 | int err; |
681 | |
682 | KASSERT(ad != NULL && ad->ad_root != NULL); |
683 | KASSERT((ad->ad_flags & ACPI_DEVICE_POWER) != 0); |
684 | |
685 | if (acpi_power_acpinode == CTL_EOL || |
686 | acpi_power_powernode == CTL_EOL) |
687 | return; |
688 | |
689 | if (ad->ad_device != NULL) |
690 | str = device_xname(ad->ad_device); |
691 | else { |
692 | dev = acpi_pcidev_find_dev(ad); |
693 | |
694 | if (dev != NULL) |
695 | str = device_xname(dev); |
696 | } |
697 | |
698 | if (str == NULL) |
699 | return; |
700 | |
701 | err = sysctl_createv(NULL, 0, NULL, NULL, |
702 | CTLFLAG_READONLY, CTLTYPE_STRING, str, |
703 | NULL, acpi_power_sysctl, 0, (void *)ad, 0, CTL_HW, |
704 | acpi_power_acpinode, acpi_power_powernode, |
705 | CTL_CREATE, CTL_EOL); |
706 | |
707 | if (err != 0) |
708 | aprint_error_dev(ad->ad_root, "sysctl_createv" |
709 | "(hw.acpi.power.%s) failed (err %d)\n" , str, err); |
710 | } |
711 | |
712 | static int |
713 | acpi_power_sysctl(SYSCTLFN_ARGS) |
714 | { |
715 | struct acpi_devnode *ad; |
716 | struct sysctlnode node; |
717 | int err, state; |
718 | char t[3]; |
719 | |
720 | node = *rnode; |
721 | ad = rnode->sysctl_data; |
722 | |
723 | if (acpi_power_get(ad->ad_handle, &state) != true) |
724 | state = 0; |
725 | |
726 | (void)memset(t, '\0', sizeof(t)); |
727 | (void)snprintf(t, sizeof(t), "D%d" , state); |
728 | |
729 | node.sysctl_data = &t; |
730 | |
731 | err = sysctl_lookup(SYSCTLFN_CALL(&node)); |
732 | |
733 | if (err || newp == NULL) |
734 | return err; |
735 | |
736 | return 0; |
737 | } |
738 | |
739 | /* |
740 | * XXX: Move this to acpi_util.c by refactoring |
741 | * acpi_name() to optionally return a single name. |
742 | */ |
743 | static const char * |
744 | acpi_xname(ACPI_HANDLE hdl) |
745 | { |
746 | static char str[5]; |
747 | ACPI_BUFFER buf; |
748 | ACPI_STATUS rv; |
749 | |
750 | buf.Pointer = str; |
751 | buf.Length = sizeof(str); |
752 | |
753 | rv = AcpiGetName(hdl, ACPI_SINGLE_NAME, &buf); |
754 | |
755 | if (ACPI_FAILURE(rv)) |
756 | return "????" ; |
757 | |
758 | return str; |
759 | } |
760 | |