Commit | Line | Data |
---|---|---|
5ed44076 MD |
1 | /*- |
2 | * Copyright (c) 2001 Michael Smith | |
3 | * All rights reserved. | |
4 | * | |
5 | * Redistribution and use in source and binary forms, with or without | |
6 | * modification, are permitted provided that the following conditions | |
7 | * are met: | |
8 | * 1. Redistributions of source code must retain the above copyright | |
9 | * notice, this list of conditions and the following disclaimer. | |
10 | * 2. Redistributions in binary form must reproduce the above copyright | |
11 | * notice, this list of conditions and the following disclaimer in the | |
12 | * documentation and/or other materials provided with the distribution. | |
13 | * | |
14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | |
15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | |
18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
24 | * SUCH DAMAGE. | |
32af04f7 SW |
25 | * |
26 | * $FreeBSD: src/sys/dev/acpica/acpi_powerres.c,v 1.30.8.1 2009/04/15 03:14:26 kensmith Exp $ | |
5ed44076 MD |
27 | */ |
28 | ||
29 | #include "opt_acpi.h" | |
30 | #include <sys/param.h> | |
31 | #include <sys/kernel.h> | |
32 | #include <sys/malloc.h> | |
33 | #include <sys/bus.h> | |
34 | ||
35 | #include "acpi.h" | |
36 | #include <dev/acpica5/acpivar.h> | |
37 | ||
38 | /* | |
39 | * ACPI power resource management. | |
40 | * | |
41 | * Power resource behaviour is slightly complicated by the fact that | |
42 | * a single power resource may provide power for more than one device. | |
43 | * Thus, we must track the device(s) being powered by a given power | |
44 | * resource, and only deactivate it when there are no powered devices. | |
45 | * | |
46 | * Note that this only manages resources for known devices. There is an | |
47 | * ugly case where we may turn of power to a device which is in use because | |
48 | * we don't know that it depends on a given resource. We should perhaps | |
49 | * try to be smarter about this, but a more complete solution would involve | |
50 | * scanning all of the ACPI namespace to find devices we're not currently | |
51 | * aware of, and this raises questions about whether they should be left | |
52 | * on, turned off, etc. | |
5ed44076 MD |
53 | */ |
54 | ||
55 | MALLOC_DEFINE(M_ACPIPWR, "acpipwr", "ACPI power resources"); | |
56 | ||
57 | /* Hooks for the ACPI CA debugging infrastructure */ | |
f9d8cd12 | 58 | #define _COMPONENT ACPI_POWERRES |
5ed44076 MD |
59 | ACPI_MODULE_NAME("POWERRES") |
60 | ||
61 | /* Return values from _STA on a power resource */ | |
62 | #define ACPI_PWR_OFF 0 | |
63 | #define ACPI_PWR_ON 1 | |
10f97674 | 64 | #define ACPI_PWR_UNK (-1) |
5ed44076 MD |
65 | |
66 | /* A relationship between a power resource and a consumer. */ | |
67 | struct acpi_powerreference { | |
68 | struct acpi_powerconsumer *ar_consumer; | |
69 | struct acpi_powerresource *ar_resource; | |
70 | TAILQ_ENTRY(acpi_powerreference) ar_rlink; /* link on resource list */ | |
71 | TAILQ_ENTRY(acpi_powerreference) ar_clink; /* link on consumer */ | |
72 | }; | |
73 | ||
74 | /* A power-managed device. */ | |
75 | struct acpi_powerconsumer { | |
76 | /* Device which is powered */ | |
77 | ACPI_HANDLE ac_consumer; | |
78 | int ac_state; | |
79 | TAILQ_ENTRY(acpi_powerconsumer) ac_link; | |
80 | TAILQ_HEAD(,acpi_powerreference) ac_references; | |
81 | }; | |
82 | ||
83 | /* A power resource. */ | |
84 | struct acpi_powerresource { | |
85 | TAILQ_ENTRY(acpi_powerresource) ap_link; | |
86 | TAILQ_HEAD(,acpi_powerreference) ap_references; | |
87 | ACPI_HANDLE ap_resource; | |
88 | ACPI_INTEGER ap_systemlevel; | |
89 | ACPI_INTEGER ap_order; | |
10f97674 | 90 | int ap_state; |
5ed44076 MD |
91 | }; |
92 | ||
93 | static TAILQ_HEAD(acpi_powerresource_list, acpi_powerresource) | |
94 | acpi_powerresources; | |
95 | static TAILQ_HEAD(acpi_powerconsumer_list, acpi_powerconsumer) | |
96 | acpi_powerconsumers; | |
10f97674 | 97 | ACPI_SERIAL_DECL(powerres, "ACPI power resources"); |
5ed44076 MD |
98 | |
99 | static ACPI_STATUS acpi_pwr_register_consumer(ACPI_HANDLE consumer); | |
f9d8cd12 | 100 | #ifdef notyet |
5ed44076 | 101 | static ACPI_STATUS acpi_pwr_deregister_consumer(ACPI_HANDLE consumer); |
f9d8cd12 | 102 | #endif /* notyet */ |
5ed44076 | 103 | static ACPI_STATUS acpi_pwr_register_resource(ACPI_HANDLE res); |
f9d8cd12 | 104 | #ifdef notyet |
5ed44076 | 105 | static ACPI_STATUS acpi_pwr_deregister_resource(ACPI_HANDLE res); |
f9d8cd12 | 106 | #endif /* notyet */ |
5ed44076 MD |
107 | static void acpi_pwr_reference_resource(ACPI_OBJECT *obj, |
108 | void *arg); | |
10f97674 AP |
109 | static int acpi_pwr_dereference_resource(struct acpi_powerconsumer |
110 | *pc); | |
5ed44076 MD |
111 | static ACPI_STATUS acpi_pwr_switch_power(void); |
112 | static struct acpi_powerresource | |
113 | *acpi_pwr_find_resource(ACPI_HANDLE res); | |
114 | static struct acpi_powerconsumer | |
115 | *acpi_pwr_find_consumer(ACPI_HANDLE consumer); | |
116 | ||
117 | /* Initialise our lists. */ | |
118 | static void | |
119 | acpi_pwr_init(void *junk) | |
120 | { | |
5c7ffd75 | 121 | ACPI_SERIAL_INIT(powerres); |
5ed44076 MD |
122 | TAILQ_INIT(&acpi_powerresources); |
123 | TAILQ_INIT(&acpi_powerconsumers); | |
124 | } | |
5c7ffd75 | 125 | SYSINIT(acpi_powerresource, SI_BOOT1_TUNABLES, SI_ORDER_ANY, acpi_pwr_init, NULL); |
5ed44076 MD |
126 | |
127 | /* | |
128 | * Register a power resource. | |
129 | * | |
130 | * It's OK to call this if we already know about the resource. | |
131 | */ | |
132 | static ACPI_STATUS | |
133 | acpi_pwr_register_resource(ACPI_HANDLE res) | |
134 | { | |
135 | ACPI_STATUS status; | |
136 | ACPI_BUFFER buf; | |
137 | ACPI_OBJECT *obj; | |
138 | struct acpi_powerresource *rp, *srp; | |
139 | ||
140 | ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); | |
10f97674 | 141 | ACPI_SERIAL_ASSERT(powerres); |
5ed44076 MD |
142 | |
143 | rp = NULL; | |
144 | buf.Pointer = NULL; | |
145 | ||
146 | /* Look to see if we know about this resource */ | |
147 | if (acpi_pwr_find_resource(res) != NULL) | |
148 | return_ACPI_STATUS (AE_OK); /* already know about it */ | |
149 | ||
150 | /* Allocate a new resource */ | |
10f97674 AP |
151 | if ((rp = kmalloc(sizeof(*rp), M_ACPIPWR, M_NOWAIT | M_ZERO)) == NULL) { |
152 | status = AE_NO_MEMORY; | |
153 | goto out; | |
154 | } | |
5ed44076 MD |
155 | TAILQ_INIT(&rp->ap_references); |
156 | rp->ap_resource = res; | |
157 | ||
158 | /* Get the Power Resource object */ | |
159 | buf.Length = ACPI_ALLOCATE_BUFFER; | |
160 | if (ACPI_FAILURE(status = AcpiEvaluateObject(res, NULL, NULL, &buf))) { | |
161 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "no power resource object\n")); | |
162 | goto out; | |
163 | } | |
164 | obj = buf.Pointer; | |
165 | if (obj->Type != ACPI_TYPE_POWER) { | |
166 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, | |
167 | "questionable power resource object %s\n", | |
168 | acpi_name(res))); | |
169 | status = AE_TYPE; | |
170 | goto out; | |
171 | } | |
172 | rp->ap_systemlevel = obj->PowerResource.SystemLevel; | |
173 | rp->ap_order = obj->PowerResource.ResourceOrder; | |
10f97674 | 174 | rp->ap_state = ACPI_PWR_UNK; |
5ed44076 MD |
175 | |
176 | /* Sort the resource into the list */ | |
177 | status = AE_OK; | |
178 | srp = TAILQ_FIRST(&acpi_powerresources); | |
179 | if (srp == NULL || rp->ap_order < srp->ap_order) { | |
180 | TAILQ_INSERT_HEAD(&acpi_powerresources, rp, ap_link); | |
181 | goto done; | |
182 | } | |
183 | TAILQ_FOREACH(srp, &acpi_powerresources, ap_link) { | |
184 | if (rp->ap_order < srp->ap_order) { | |
185 | TAILQ_INSERT_BEFORE(srp, rp, ap_link); | |
186 | goto done; | |
187 | } | |
188 | } | |
189 | TAILQ_INSERT_TAIL(&acpi_powerresources, rp, ap_link); | |
190 | ||
191 | done: | |
192 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, | |
193 | "registered power resource %s\n", acpi_name(res))); | |
194 | ||
195 | out: | |
196 | if (buf.Pointer != NULL) | |
197 | AcpiOsFree(buf.Pointer); | |
198 | if (ACPI_FAILURE(status) && rp != NULL) | |
efda3bd0 | 199 | kfree(rp, M_ACPIPWR); |
5ed44076 MD |
200 | return_ACPI_STATUS (status); |
201 | } | |
202 | ||
f9d8cd12 | 203 | #ifdef notyet |
5ed44076 MD |
204 | /* |
205 | * Deregister a power resource. | |
206 | */ | |
207 | static ACPI_STATUS | |
208 | acpi_pwr_deregister_resource(ACPI_HANDLE res) | |
209 | { | |
210 | struct acpi_powerresource *rp; | |
211 | ||
212 | ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); | |
10f97674 | 213 | ACPI_SERIAL_ASSERT(powerres); |
5ed44076 MD |
214 | |
215 | rp = NULL; | |
216 | ||
217 | /* Find the resource */ | |
218 | if ((rp = acpi_pwr_find_resource(res)) == NULL) | |
219 | return_ACPI_STATUS (AE_BAD_PARAMETER); | |
220 | ||
221 | /* Check that there are no consumers referencing this resource */ | |
222 | if (TAILQ_FIRST(&rp->ap_references) != NULL) | |
223 | return_ACPI_STATUS (AE_BAD_PARAMETER); | |
224 | ||
10f97674 | 225 | /* Pull it off the list and kfree it */ |
5ed44076 | 226 | TAILQ_REMOVE(&acpi_powerresources, rp, ap_link); |
efda3bd0 | 227 | kfree(rp, M_ACPIPWR); |
5ed44076 MD |
228 | |
229 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "deregistered power resource %s\n", | |
230 | acpi_name(res))); | |
231 | ||
232 | return_ACPI_STATUS (AE_OK); | |
233 | } | |
f9d8cd12 | 234 | #endif /* notyet */ |
5ed44076 MD |
235 | |
236 | /* | |
237 | * Register a power consumer. | |
238 | * | |
239 | * It's OK to call this if we already know about the consumer. | |
240 | */ | |
241 | static ACPI_STATUS | |
242 | acpi_pwr_register_consumer(ACPI_HANDLE consumer) | |
243 | { | |
244 | struct acpi_powerconsumer *pc; | |
245 | ||
246 | ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); | |
10f97674 | 247 | ACPI_SERIAL_ASSERT(powerres); |
5ed44076 MD |
248 | |
249 | /* Check to see whether we know about this consumer already */ | |
10f97674 | 250 | if (acpi_pwr_find_consumer(consumer) != NULL) |
5ed44076 MD |
251 | return_ACPI_STATUS (AE_OK); |
252 | ||
253 | /* Allocate a new power consumer */ | |
10f97674 AP |
254 | if ((pc = kmalloc(sizeof(*pc), M_ACPIPWR, M_NOWAIT)) == NULL) |
255 | return_ACPI_STATUS (AE_NO_MEMORY); | |
5ed44076 MD |
256 | TAILQ_INSERT_HEAD(&acpi_powerconsumers, pc, ac_link); |
257 | TAILQ_INIT(&pc->ac_references); | |
258 | pc->ac_consumer = consumer; | |
259 | ||
260 | /* XXX we should try to find its current state */ | |
261 | pc->ac_state = ACPI_STATE_UNKNOWN; | |
262 | ||
263 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "registered power consumer %s\n", | |
264 | acpi_name(consumer))); | |
265 | ||
266 | return_ACPI_STATUS (AE_OK); | |
267 | } | |
268 | ||
f9d8cd12 | 269 | #ifdef notyet |
5ed44076 MD |
270 | /* |
271 | * Deregister a power consumer. | |
272 | * | |
273 | * This should only be done once the consumer has been powered off. | |
274 | * (XXX is this correct? Check once implemented) | |
275 | */ | |
276 | static ACPI_STATUS | |
277 | acpi_pwr_deregister_consumer(ACPI_HANDLE consumer) | |
278 | { | |
279 | struct acpi_powerconsumer *pc; | |
280 | ||
281 | ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); | |
10f97674 | 282 | ACPI_SERIAL_ASSERT(powerres); |
5ed44076 MD |
283 | |
284 | /* Find the consumer */ | |
285 | if ((pc = acpi_pwr_find_consumer(consumer)) == NULL) | |
286 | return_ACPI_STATUS (AE_BAD_PARAMETER); | |
287 | ||
288 | /* Make sure the consumer's not referencing anything right now */ | |
289 | if (TAILQ_FIRST(&pc->ac_references) != NULL) | |
290 | return_ACPI_STATUS (AE_BAD_PARAMETER); | |
291 | ||
10f97674 | 292 | /* Pull the consumer off the list and kfree it */ |
5ed44076 | 293 | TAILQ_REMOVE(&acpi_powerconsumers, pc, ac_link); |
10f97674 | 294 | kfree(pc, M_ACPIPWR); |
5ed44076 MD |
295 | |
296 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "deregistered power consumer %s\n", | |
297 | acpi_name(consumer))); | |
298 | ||
299 | return_ACPI_STATUS (AE_OK); | |
300 | } | |
f9d8cd12 | 301 | #endif /* notyet */ |
5ed44076 MD |
302 | |
303 | /* | |
304 | * Set a power consumer to a particular power state. | |
305 | */ | |
306 | ACPI_STATUS | |
307 | acpi_pwr_switch_consumer(ACPI_HANDLE consumer, int state) | |
308 | { | |
309 | struct acpi_powerconsumer *pc; | |
5ed44076 MD |
310 | ACPI_HANDLE method_handle, reslist_handle, pr0_handle; |
311 | ACPI_BUFFER reslist_buffer; | |
312 | ACPI_OBJECT *reslist_object; | |
313 | ACPI_STATUS status; | |
314 | char *method_name, *reslist_name; | |
315 | int res_changed; | |
316 | ||
317 | ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); | |
318 | ||
f9d8cd12 MD |
319 | /* It's never ok to switch a non-existent consumer. */ |
320 | if (consumer == NULL) | |
321 | return_ACPI_STATUS (AE_NOT_FOUND); | |
10f97674 AP |
322 | reslist_buffer.Pointer = NULL; |
323 | reslist_object = NULL; | |
324 | ACPI_SERIAL_BEGIN(powerres); | |
f9d8cd12 | 325 | |
5ed44076 MD |
326 | /* Find the consumer */ |
327 | if ((pc = acpi_pwr_find_consumer(consumer)) == NULL) { | |
328 | if (ACPI_FAILURE(status = acpi_pwr_register_consumer(consumer))) | |
10f97674 AP |
329 | goto out; |
330 | if ((pc = acpi_pwr_find_consumer(consumer)) == NULL) | |
331 | panic("acpi added power consumer but can't find it"); | |
5ed44076 MD |
332 | } |
333 | ||
10f97674 AP |
334 | /* Check for valid transitions. We can only go to D0 from D3. */ |
335 | status = AE_BAD_PARAMETER; | |
5ed44076 | 336 | if (pc->ac_state == ACPI_STATE_D3 && state != ACPI_STATE_D0) |
10f97674 | 337 | goto out; |
5ed44076 MD |
338 | |
339 | /* Find transition mechanism(s) */ | |
10f97674 | 340 | switch (state) { |
5ed44076 MD |
341 | case ACPI_STATE_D0: |
342 | method_name = "_PS0"; | |
343 | reslist_name = "_PR0"; | |
344 | break; | |
345 | case ACPI_STATE_D1: | |
346 | method_name = "_PS1"; | |
347 | reslist_name = "_PR1"; | |
348 | break; | |
349 | case ACPI_STATE_D2: | |
350 | method_name = "_PS2"; | |
351 | reslist_name = "_PR2"; | |
352 | break; | |
353 | case ACPI_STATE_D3: | |
354 | method_name = "_PS3"; | |
355 | reslist_name = "_PR3"; | |
356 | break; | |
357 | default: | |
10f97674 | 358 | goto out; |
5ed44076 MD |
359 | } |
360 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "setup to switch %s D%d -> D%d\n", | |
361 | acpi_name(consumer), pc->ac_state, state)); | |
362 | ||
363 | /* | |
364 | * Verify that this state is supported, ie. one of method or | |
365 | * reslist must be present. We need to do this before we go | |
366 | * dereferencing resources (since we might be trying to go to | |
367 | * a state we don't support). | |
368 | * | |
369 | * Note that if any states are supported, the device has to | |
370 | * support D0 and D3. It's never an error to try to go to | |
371 | * D0. | |
372 | */ | |
5ed44076 MD |
373 | if (ACPI_FAILURE(AcpiGetHandle(consumer, method_name, &method_handle))) |
374 | method_handle = NULL; | |
375 | if (ACPI_FAILURE(AcpiGetHandle(consumer, reslist_name, &reslist_handle))) | |
376 | reslist_handle = NULL; | |
377 | if (reslist_handle == NULL && method_handle == NULL) { | |
378 | if (state == ACPI_STATE_D0) { | |
379 | pc->ac_state = ACPI_STATE_D0; | |
10f97674 AP |
380 | status = AE_OK; |
381 | goto out; | |
382 | } | |
383 | if (state != ACPI_STATE_D3) { | |
384 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, | |
385 | "attempt to set unsupported state D%d\n", state)); | |
386 | goto out; | |
5ed44076 | 387 | } |
5ed44076 | 388 | |
10f97674 AP |
389 | /* |
390 | * Turn off the resources listed in _PR0 to go to D3. If there is | |
391 | * no _PR0 method, this object doesn't support ACPI power states. | |
392 | */ | |
393 | if (ACPI_FAILURE(AcpiGetHandle(consumer, "_PR0", &pr0_handle))) { | |
394 | status = AE_NOT_FOUND; | |
395 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, | |
396 | "device missing _PR0 (desired state was D%d)\n", state)); | |
397 | goto out; | |
398 | } | |
5ed44076 MD |
399 | reslist_buffer.Length = ACPI_ALLOCATE_BUFFER; |
400 | status = AcpiEvaluateObject(pr0_handle, NULL, NULL, &reslist_buffer); | |
10f97674 AP |
401 | if (ACPI_FAILURE(status)) { |
402 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, | |
403 | "can't evaluate _PR0 for device %s, state D%d\n", | |
404 | acpi_name(consumer), state)); | |
405 | goto out; | |
406 | } | |
5ed44076 | 407 | reslist_object = (ACPI_OBJECT *)reslist_buffer.Pointer; |
10f97674 AP |
408 | if (!ACPI_PKG_VALID(reslist_object, 1)) { |
409 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, | |
410 | "invalid package object for state D%d\n", state)); | |
411 | status = AE_TYPE; | |
412 | goto out; | |
5ed44076 MD |
413 | } |
414 | AcpiOsFree(reslist_buffer.Pointer); | |
415 | reslist_buffer.Pointer = NULL; | |
416 | reslist_object = NULL; | |
417 | } | |
418 | ||
419 | /* | |
420 | * Check that we can actually fetch the list of power resources | |
421 | */ | |
422 | if (reslist_handle != NULL) { | |
423 | reslist_buffer.Length = ACPI_ALLOCATE_BUFFER; | |
424 | status = AcpiEvaluateObject(reslist_handle, NULL, NULL, | |
425 | &reslist_buffer); | |
426 | if (ACPI_FAILURE(status)) { | |
427 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, | |
428 | "can't evaluate resource list %s\n", | |
429 | acpi_name(reslist_handle))); | |
430 | goto out; | |
431 | } | |
432 | reslist_object = (ACPI_OBJECT *)reslist_buffer.Pointer; | |
433 | if (reslist_object->Type != ACPI_TYPE_PACKAGE) { | |
434 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, | |
435 | "resource list is not ACPI_TYPE_PACKAGE (%d)\n", | |
436 | reslist_object->Type)); | |
437 | status = AE_TYPE; | |
438 | goto out; | |
439 | } | |
440 | } | |
441 | ||
442 | /* | |
443 | * Now we are ready to switch, so kill off any current power | |
444 | * resource references. | |
445 | */ | |
10f97674 | 446 | res_changed = acpi_pwr_dereference_resource(pc); |
5ed44076 MD |
447 | |
448 | /* | |
449 | * Add new power resource references, if we have any. Traverse the | |
450 | * package that we got from evaluating reslist_handle, and look up each | |
451 | * of the resources that are referenced. | |
452 | */ | |
453 | if (reslist_object != NULL) { | |
454 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "referencing %d new resources\n", | |
455 | reslist_object->Package.Count)); | |
456 | acpi_ForeachPackageObject(reslist_object, acpi_pwr_reference_resource, | |
457 | pc); | |
458 | res_changed = 1; | |
459 | } | |
460 | ||
461 | /* | |
462 | * If we changed anything in the resource list, we need to run a switch | |
463 | * pass now. | |
464 | */ | |
465 | if (ACPI_FAILURE(status = acpi_pwr_switch_power())) { | |
466 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, | |
467 | "failed to switch resources from %s to D%d\n", | |
468 | acpi_name(consumer), state)); | |
469 | ||
470 | /* XXX is this appropriate? Should we return to previous state? */ | |
10f97674 | 471 | goto out; |
5ed44076 MD |
472 | } |
473 | ||
474 | /* Invoke power state switch method (if present) */ | |
475 | if (method_handle != NULL) { | |
476 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, | |
477 | "invoking state transition method %s\n", | |
478 | acpi_name(method_handle))); | |
479 | status = AcpiEvaluateObject(method_handle, NULL, NULL, NULL); | |
480 | if (ACPI_FAILURE(status)) { | |
481 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "failed to set state - %s\n", | |
482 | AcpiFormatException(status))); | |
483 | pc->ac_state = ACPI_STATE_UNKNOWN; | |
484 | ||
485 | /* XXX Should we return to previous state? */ | |
486 | goto out; | |
487 | } | |
488 | } | |
10f97674 | 489 | |
5ed44076 MD |
490 | /* Transition was successful */ |
491 | pc->ac_state = state; | |
10f97674 | 492 | status = AE_OK; |
5ed44076 | 493 | |
10f97674 AP |
494 | out: |
495 | ACPI_SERIAL_END(powerres); | |
5ed44076 MD |
496 | if (reslist_buffer.Pointer != NULL) |
497 | AcpiOsFree(reslist_buffer.Pointer); | |
498 | return_ACPI_STATUS (status); | |
499 | } | |
500 | ||
10f97674 AP |
501 | /* Enable or disable a power resource for wake */ |
502 | ACPI_STATUS | |
503 | acpi_pwr_wake_enable(ACPI_HANDLE consumer, int enable) | |
504 | { | |
505 | ACPI_STATUS status; | |
506 | struct acpi_powerconsumer *pc; | |
507 | struct acpi_prw_data prw; | |
508 | int i; | |
509 | ||
510 | ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); | |
511 | ||
512 | if (consumer == NULL) | |
513 | return (AE_BAD_PARAMETER); | |
514 | ||
515 | ACPI_SERIAL_BEGIN(powerres); | |
516 | if ((pc = acpi_pwr_find_consumer(consumer)) == NULL) { | |
517 | if (ACPI_FAILURE(status = acpi_pwr_register_consumer(consumer))) | |
518 | goto out; | |
519 | if ((pc = acpi_pwr_find_consumer(consumer)) == NULL) | |
520 | panic("acpi wake added power consumer but can't find it"); | |
521 | } | |
522 | ||
523 | status = AE_OK; | |
524 | if (acpi_parse_prw(consumer, &prw) != 0) | |
525 | goto out; | |
526 | for (i = 0; i < prw.power_res_count; i++) | |
527 | if (enable) | |
528 | acpi_pwr_reference_resource(&prw.power_res[i], pc); | |
529 | else | |
530 | acpi_pwr_dereference_resource(pc); | |
531 | ||
532 | if (prw.power_res_count > 0) | |
533 | acpi_pwr_switch_power(); | |
534 | ||
535 | out: | |
536 | ACPI_SERIAL_END(powerres); | |
537 | return (status); | |
538 | } | |
539 | ||
5ed44076 MD |
540 | /* |
541 | * Called to create a reference between a power consumer and a power resource | |
542 | * identified in the object. | |
543 | */ | |
544 | static void | |
545 | acpi_pwr_reference_resource(ACPI_OBJECT *obj, void *arg) | |
546 | { | |
547 | struct acpi_powerconsumer *pc = (struct acpi_powerconsumer *)arg; | |
548 | struct acpi_powerreference *pr; | |
549 | struct acpi_powerresource *rp; | |
550 | ACPI_HANDLE res; | |
551 | ACPI_STATUS status; | |
552 | ||
553 | ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); | |
10f97674 | 554 | ACPI_SERIAL_ASSERT(powerres); |
5ed44076 | 555 | |
f9d8cd12 MD |
556 | res = acpi_GetReference(NULL, obj); |
557 | if (res == NULL) { | |
5ed44076 | 558 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, |
f9d8cd12 | 559 | "can't create a power reference for object type %d\n", |
5ed44076 MD |
560 | obj->Type)); |
561 | return_VOID; | |
562 | } | |
563 | ||
564 | /* Create/look up the resource */ | |
565 | if (ACPI_FAILURE(status = acpi_pwr_register_resource(res))) { | |
566 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, | |
567 | "couldn't register power resource %s - %s\n", | |
568 | obj->String.Pointer, AcpiFormatException(status))); | |
569 | return_VOID; | |
570 | } | |
571 | if ((rp = acpi_pwr_find_resource(res)) == NULL) { | |
572 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "power resource list corrupted\n")); | |
573 | return_VOID; | |
574 | } | |
575 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "found power resource %s\n", | |
576 | acpi_name(rp->ap_resource))); | |
577 | ||
578 | /* Create a reference between the consumer and resource */ | |
10f97674 AP |
579 | if ((pr = kmalloc(sizeof(*pr), M_ACPIPWR, M_NOWAIT | M_ZERO)) == NULL) { |
580 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, | |
581 | "allocation failed for a power consumer reference\n")); | |
582 | return_VOID; | |
583 | } | |
5ed44076 MD |
584 | pr->ar_consumer = pc; |
585 | pr->ar_resource = rp; | |
586 | TAILQ_INSERT_TAIL(&pc->ac_references, pr, ar_clink); | |
587 | TAILQ_INSERT_TAIL(&rp->ap_references, pr, ar_rlink); | |
10f97674 | 588 | |
5ed44076 MD |
589 | return_VOID; |
590 | } | |
591 | ||
10f97674 AP |
592 | static int |
593 | acpi_pwr_dereference_resource(struct acpi_powerconsumer *pc) | |
594 | { | |
595 | struct acpi_powerreference *pr; | |
596 | int changed; | |
597 | ||
598 | ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); | |
599 | ACPI_SERIAL_ASSERT(powerres); | |
600 | ||
601 | changed = 0; | |
602 | while ((pr = TAILQ_FIRST(&pc->ac_references)) != NULL) { | |
603 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "removing reference to %s\n", | |
604 | acpi_name(pr->ar_resource->ap_resource))); | |
605 | TAILQ_REMOVE(&pr->ar_resource->ap_references, pr, ar_rlink); | |
606 | TAILQ_REMOVE(&pc->ac_references, pr, ar_clink); | |
607 | kfree(pr, M_ACPIPWR); | |
608 | changed = 1; | |
609 | } | |
610 | ||
611 | return (changed); | |
612 | } | |
5ed44076 MD |
613 | |
614 | /* | |
615 | * Switch power resources to conform to the desired state. | |
616 | * | |
617 | * Consumers may have modified the power resource list in an arbitrary | |
618 | * fashion; we sweep it in sequence order. | |
619 | */ | |
620 | static ACPI_STATUS | |
621 | acpi_pwr_switch_power(void) | |
622 | { | |
623 | struct acpi_powerresource *rp; | |
624 | ACPI_STATUS status; | |
625 | int cur; | |
626 | ||
627 | ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); | |
10f97674 | 628 | ACPI_SERIAL_ASSERT(powerres); |
5ed44076 MD |
629 | |
630 | /* | |
631 | * Sweep the list forwards turning things on. | |
632 | */ | |
633 | TAILQ_FOREACH(rp, &acpi_powerresources, ap_link) { | |
634 | if (TAILQ_FIRST(&rp->ap_references) == NULL) { | |
635 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, | |
636 | "%s has no references, not turning on\n", | |
637 | acpi_name(rp->ap_resource))); | |
638 | continue; | |
639 | } | |
640 | ||
641 | /* We could cache this if we trusted it not to change under us */ | |
f9d8cd12 | 642 | status = acpi_GetInteger(rp->ap_resource, "_STA", &cur); |
5ed44076 MD |
643 | if (ACPI_FAILURE(status)) { |
644 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "can't get status of %s - %d\n", | |
645 | acpi_name(rp->ap_resource), status)); | |
5ed44076 MD |
646 | /* XXX is this correct? Always switch if in doubt? */ |
647 | continue; | |
10f97674 AP |
648 | } else if (rp->ap_state == ACPI_PWR_UNK) |
649 | rp->ap_state = cur; | |
5ed44076 MD |
650 | |
651 | /* | |
652 | * Switch if required. Note that we ignore the result of the switch | |
653 | * effort; we don't know what to do if it fails, so checking wouldn't | |
654 | * help much. | |
655 | */ | |
10f97674 | 656 | if (rp->ap_state != ACPI_PWR_ON) { |
5ed44076 MD |
657 | status = AcpiEvaluateObject(rp->ap_resource, "_ON", NULL, NULL); |
658 | if (ACPI_FAILURE(status)) { | |
659 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, | |
660 | "failed to switch %s on - %s\n", | |
661 | acpi_name(rp->ap_resource), | |
662 | AcpiFormatException(status))); | |
663 | } else { | |
10f97674 | 664 | rp->ap_state = ACPI_PWR_ON; |
5ed44076 MD |
665 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "switched %s on\n", |
666 | acpi_name(rp->ap_resource))); | |
667 | } | |
668 | } else { | |
669 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "%s is already on\n", | |
670 | acpi_name(rp->ap_resource))); | |
671 | } | |
672 | } | |
673 | ||
674 | /* Sweep the list backwards turning things off. */ | |
675 | TAILQ_FOREACH_REVERSE(rp, &acpi_powerresources, acpi_powerresource_list, | |
676 | ap_link) { | |
677 | ||
678 | if (TAILQ_FIRST(&rp->ap_references) != NULL) { | |
679 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, | |
680 | "%s has references, not turning off\n", | |
681 | acpi_name(rp->ap_resource))); | |
682 | continue; | |
683 | } | |
684 | ||
685 | /* We could cache this if we trusted it not to change under us */ | |
f9d8cd12 | 686 | status = acpi_GetInteger(rp->ap_resource, "_STA", &cur); |
5ed44076 MD |
687 | if (ACPI_FAILURE(status)) { |
688 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "can't get status of %s - %d\n", | |
689 | acpi_name(rp->ap_resource), status)); | |
690 | /* XXX is this correct? Always switch if in doubt? */ | |
691 | continue; | |
10f97674 AP |
692 | } else if (rp->ap_state == ACPI_PWR_UNK) |
693 | rp->ap_state = cur; | |
5ed44076 MD |
694 | |
695 | /* | |
696 | * Switch if required. Note that we ignore the result of the switch | |
697 | * effort; we don't know what to do if it fails, so checking wouldn't | |
698 | * help much. | |
699 | */ | |
10f97674 | 700 | if (rp->ap_state != ACPI_PWR_OFF) { |
5ed44076 MD |
701 | status = AcpiEvaluateObject(rp->ap_resource, "_OFF", NULL, NULL); |
702 | if (ACPI_FAILURE(status)) { | |
703 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, | |
704 | "failed to switch %s off - %s\n", | |
705 | acpi_name(rp->ap_resource), | |
706 | AcpiFormatException(status))); | |
707 | } else { | |
10f97674 | 708 | rp->ap_state = ACPI_PWR_OFF; |
5ed44076 MD |
709 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "switched %s off\n", |
710 | acpi_name(rp->ap_resource))); | |
711 | } | |
712 | } else { | |
713 | ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "%s is already off\n", | |
714 | acpi_name(rp->ap_resource))); | |
715 | } | |
716 | } | |
717 | ||
718 | return_ACPI_STATUS (AE_OK); | |
719 | } | |
720 | ||
721 | /* | |
722 | * Find a power resource's control structure. | |
723 | */ | |
724 | static struct acpi_powerresource * | |
725 | acpi_pwr_find_resource(ACPI_HANDLE res) | |
726 | { | |
727 | struct acpi_powerresource *rp; | |
728 | ||
729 | ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); | |
10f97674 | 730 | ACPI_SERIAL_ASSERT(powerres); |
5ed44076 MD |
731 | |
732 | TAILQ_FOREACH(rp, &acpi_powerresources, ap_link) { | |
733 | if (rp->ap_resource == res) | |
734 | break; | |
735 | } | |
736 | ||
737 | return_PTR (rp); | |
738 | } | |
739 | ||
740 | /* | |
741 | * Find a power consumer's control structure. | |
742 | */ | |
743 | static struct acpi_powerconsumer * | |
744 | acpi_pwr_find_consumer(ACPI_HANDLE consumer) | |
745 | { | |
746 | struct acpi_powerconsumer *pc; | |
747 | ||
748 | ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); | |
10f97674 | 749 | ACPI_SERIAL_ASSERT(powerres); |
5ed44076 MD |
750 | |
751 | TAILQ_FOREACH(pc, &acpi_powerconsumers, ac_link) { | |
752 | if (pc->ac_consumer == consumer) | |
753 | break; | |
754 | } | |
755 | ||
756 | return_PTR (pc); | |
757 | } |