1*ef019da3Smrg /* $NetBSD: acpi_power.c,v 1.36 2022/05/31 20:28:57 mrg Exp $ */
29ddf2c51Sjruoho
39ddf2c51Sjruoho /*-
443337e89Sjruoho * Copyright (c) 2009, 2010, 2011 The NetBSD Foundation, Inc.
59ddf2c51Sjruoho * All rights reserved.
69ddf2c51Sjruoho *
79ddf2c51Sjruoho * This code is derived from software contributed to The NetBSD Foundation
89ddf2c51Sjruoho * by Jukka Ruohonen.
99ddf2c51Sjruoho *
109ddf2c51Sjruoho * Redistribution and use in source and binary forms, with or without
119ddf2c51Sjruoho * modification, are permitted provided that the following conditions
129ddf2c51Sjruoho * are met:
139ddf2c51Sjruoho * 1. Redistributions of source code must retain the above copyright
149ddf2c51Sjruoho * notice, this list of conditions and the following disclaimer.
159ddf2c51Sjruoho * 2. Redistributions in binary form must reproduce the above copyright
169ddf2c51Sjruoho * notice, this list of conditions and the following disclaimer in the
179ddf2c51Sjruoho * documentation and/or other materials provided with the distribution.
189ddf2c51Sjruoho *
199ddf2c51Sjruoho * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
209ddf2c51Sjruoho * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
219ddf2c51Sjruoho * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
229ddf2c51Sjruoho * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
239ddf2c51Sjruoho * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
249ddf2c51Sjruoho * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
259ddf2c51Sjruoho * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
269ddf2c51Sjruoho * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
279ddf2c51Sjruoho * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
289ddf2c51Sjruoho * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
299ddf2c51Sjruoho * POSSIBILITY OF SUCH DAMAGE.
309ddf2c51Sjruoho */
319ddf2c51Sjruoho
329ddf2c51Sjruoho /*-
339ddf2c51Sjruoho * Copyright (c) 2001 Michael Smith
349ddf2c51Sjruoho * All rights reserved.
359ddf2c51Sjruoho *
369ddf2c51Sjruoho * Redistribution and use in source and binary forms, with or without
379ddf2c51Sjruoho * modification, are permitted provided that the following conditions
389ddf2c51Sjruoho * are met:
399ddf2c51Sjruoho * 1. Redistributions of source code must retain the above copyright
409ddf2c51Sjruoho * notice, this list of conditions and the following disclaimer.
419ddf2c51Sjruoho * 2. Redistributions in binary form must reproduce the above copyright
429ddf2c51Sjruoho * notice, this list of conditions and the following disclaimer in the
439ddf2c51Sjruoho * documentation and/or other materials provided with the distribution.
449ddf2c51Sjruoho *
459ddf2c51Sjruoho * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
469ddf2c51Sjruoho * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
479ddf2c51Sjruoho * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
489ddf2c51Sjruoho * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
499ddf2c51Sjruoho * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
509ddf2c51Sjruoho * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
519ddf2c51Sjruoho * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
529ddf2c51Sjruoho * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
539ddf2c51Sjruoho * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
549ddf2c51Sjruoho * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
559ddf2c51Sjruoho * SUCH DAMAGE.
569ddf2c51Sjruoho */
579ddf2c51Sjruoho
589ddf2c51Sjruoho #include <sys/cdefs.h>
59*ef019da3Smrg __KERNEL_RCSID(0, "$NetBSD: acpi_power.c,v 1.36 2022/05/31 20:28:57 mrg Exp $");
60*ef019da3Smrg
61*ef019da3Smrg #include "pci.h"
629ddf2c51Sjruoho
639ddf2c51Sjruoho #include <sys/param.h>
649ddf2c51Sjruoho #include <sys/kmem.h>
659ddf2c51Sjruoho #include <sys/mutex.h>
6616bdc3ffSjruoho #include <sys/sysctl.h>
679ddf2c51Sjruoho
689ddf2c51Sjruoho #include <dev/acpi/acpireg.h>
699ddf2c51Sjruoho #include <dev/acpi/acpivar.h>
70e008cf16Sjruoho #include <dev/acpi/acpi_pci.h>
719ddf2c51Sjruoho #include <dev/acpi/acpi_power.h>
729ddf2c51Sjruoho
739ddf2c51Sjruoho #define _COMPONENT ACPI_BUS_COMPONENT
749ddf2c51Sjruoho ACPI_MODULE_NAME ("acpi_power")
759ddf2c51Sjruoho
76d5124a18Sjruoho #define ACPI_STA_POW_OFF 0x00
77d5124a18Sjruoho #define ACPI_STA_POW_ON 0x01
78d5124a18Sjruoho
799ddf2c51Sjruoho struct acpi_power_res {
809ddf2c51Sjruoho ACPI_HANDLE res_handle;
819ddf2c51Sjruoho ACPI_INTEGER res_level;
829ddf2c51Sjruoho ACPI_INTEGER res_order;
8343337e89Sjruoho ACPI_HANDLE res_ref[5];
849ddf2c51Sjruoho char res_name[5];
859ddf2c51Sjruoho kmutex_t res_mutex;
869ddf2c51Sjruoho
879ddf2c51Sjruoho TAILQ_ENTRY(acpi_power_res) res_list;
889ddf2c51Sjruoho };
899ddf2c51Sjruoho
909ddf2c51Sjruoho static TAILQ_HEAD(, acpi_power_res) res_head =
919ddf2c51Sjruoho TAILQ_HEAD_INITIALIZER(res_head);
929ddf2c51Sjruoho
93646cb7e2Sgsutre static int32_t acpi_power_acpinode = CTL_EOL;
94646cb7e2Sgsutre static int32_t acpi_power_powernode = CTL_EOL;
9516bdc3ffSjruoho
969ddf2c51Sjruoho static struct acpi_power_res *acpi_power_res_init(ACPI_HANDLE);
979ddf2c51Sjruoho static struct acpi_power_res *acpi_power_res_get(ACPI_HANDLE);
989ddf2c51Sjruoho
999ddf2c51Sjruoho static ACPI_STATUS acpi_power_get_direct(struct acpi_devnode *);
1009ddf2c51Sjruoho static ACPI_STATUS acpi_power_get_indirect(struct acpi_devnode *);
1019ddf2c51Sjruoho static ACPI_STATUS acpi_power_switch(struct acpi_devnode *,
1029ddf2c51Sjruoho int, bool);
1039ddf2c51Sjruoho static ACPI_STATUS acpi_power_res_ref(struct acpi_power_res *,
1049ddf2c51Sjruoho ACPI_HANDLE);
1059ddf2c51Sjruoho static ACPI_STATUS acpi_power_res_deref(struct acpi_power_res *,
1069ddf2c51Sjruoho ACPI_HANDLE);
1079ddf2c51Sjruoho static ACPI_STATUS acpi_power_res_sta(ACPI_OBJECT *, void *);
1089ddf2c51Sjruoho
1099ddf2c51Sjruoho static ACPI_OBJECT *acpi_power_pkg_get(ACPI_HANDLE, int);
1102a35c4cfSjruoho static int acpi_power_sysctl(SYSCTLFN_PROTO);
1119ddf2c51Sjruoho static const char *acpi_xname(ACPI_HANDLE);
1129ddf2c51Sjruoho
1139ddf2c51Sjruoho static struct acpi_power_res *
acpi_power_res_init(ACPI_HANDLE hdl)1149ddf2c51Sjruoho acpi_power_res_init(ACPI_HANDLE hdl)
1159ddf2c51Sjruoho {
1169ddf2c51Sjruoho struct acpi_power_res *tmp = NULL;
1179ddf2c51Sjruoho struct acpi_power_res *res = NULL;
1189ddf2c51Sjruoho ACPI_OBJECT *obj;
1199ddf2c51Sjruoho ACPI_BUFFER buf;
1209ddf2c51Sjruoho ACPI_STATUS rv;
12143337e89Sjruoho size_t i;
1229ddf2c51Sjruoho
1239ddf2c51Sjruoho rv = acpi_eval_struct(hdl, NULL, &buf);
1249ddf2c51Sjruoho
1259ddf2c51Sjruoho if (ACPI_FAILURE(rv))
1269ddf2c51Sjruoho goto out;
1279ddf2c51Sjruoho
1289ddf2c51Sjruoho obj = buf.Pointer;
1299ddf2c51Sjruoho
1309ddf2c51Sjruoho if (obj->Type != ACPI_TYPE_POWER) {
1319ddf2c51Sjruoho rv = AE_TYPE;
1329ddf2c51Sjruoho goto out;
1339ddf2c51Sjruoho }
1349ddf2c51Sjruoho
1359ddf2c51Sjruoho res = kmem_zalloc(sizeof(*res), KM_SLEEP);
1369ddf2c51Sjruoho res->res_handle = hdl;
1379ddf2c51Sjruoho res->res_level = obj->PowerResource.SystemLevel;
1389ddf2c51Sjruoho res->res_order = obj->PowerResource.ResourceOrder;
1399ddf2c51Sjruoho
1409ddf2c51Sjruoho (void)strlcpy(res->res_name,
1419ddf2c51Sjruoho acpi_xname(hdl), sizeof(res->res_name));
1429ddf2c51Sjruoho
14343337e89Sjruoho for (i = 0; i < __arraycount(res->res_ref); i++)
14443337e89Sjruoho res->res_ref[i] = NULL;
14543337e89Sjruoho
1469ddf2c51Sjruoho mutex_init(&res->res_mutex, MUTEX_DEFAULT, IPL_NONE);
1479ddf2c51Sjruoho
1489ddf2c51Sjruoho /*
1499ddf2c51Sjruoho * Power resources should be ordered.
1509ddf2c51Sjruoho *
1519ddf2c51Sjruoho * These *should* be enabled from low values to high
1529ddf2c51Sjruoho * values and disabled from high values to low values.
1539ddf2c51Sjruoho */
1549ddf2c51Sjruoho TAILQ_FOREACH(tmp, &res_head, res_list) {
1559ddf2c51Sjruoho
1569ddf2c51Sjruoho if (res->res_order < tmp->res_order) {
1579ddf2c51Sjruoho TAILQ_INSERT_BEFORE(tmp, res, res_list);
1589ddf2c51Sjruoho break;
1599ddf2c51Sjruoho }
1609ddf2c51Sjruoho }
1619ddf2c51Sjruoho
1629ddf2c51Sjruoho if (tmp == NULL)
1639ddf2c51Sjruoho TAILQ_INSERT_TAIL(&res_head, res, res_list);
1649ddf2c51Sjruoho
16543330869Sjruoho ACPI_DEBUG_PRINT((ACPI_DB_INFO, "%s added to the "
16643330869Sjruoho "power resource queue\n", res->res_name));
16743330869Sjruoho
1689ddf2c51Sjruoho out:
1699ddf2c51Sjruoho if (buf.Pointer != NULL)
1709ddf2c51Sjruoho ACPI_FREE(buf.Pointer);
1719ddf2c51Sjruoho
1729ddf2c51Sjruoho return res;
1739ddf2c51Sjruoho }
1749ddf2c51Sjruoho
1759ddf2c51Sjruoho static struct acpi_power_res *
acpi_power_res_get(ACPI_HANDLE hdl)1769ddf2c51Sjruoho acpi_power_res_get(ACPI_HANDLE hdl)
1779ddf2c51Sjruoho {
1789ddf2c51Sjruoho struct acpi_power_res *res;
1799ddf2c51Sjruoho
1809ddf2c51Sjruoho TAILQ_FOREACH(res, &res_head, res_list) {
1819ddf2c51Sjruoho
1829ddf2c51Sjruoho if (res->res_handle == hdl)
1839ddf2c51Sjruoho return res;
1849ddf2c51Sjruoho }
1859ddf2c51Sjruoho
1869ddf2c51Sjruoho return acpi_power_res_init(hdl);
1879ddf2c51Sjruoho }
1889ddf2c51Sjruoho
1899ddf2c51Sjruoho bool
acpi_power_register(ACPI_HANDLE hdl)19020525938Sjruoho acpi_power_register(ACPI_HANDLE hdl)
1919ddf2c51Sjruoho {
1929ddf2c51Sjruoho return true;
1939ddf2c51Sjruoho }
1949ddf2c51Sjruoho
1959ddf2c51Sjruoho void
acpi_power_deregister(ACPI_HANDLE hdl)19620525938Sjruoho acpi_power_deregister(ACPI_HANDLE hdl)
1979ddf2c51Sjruoho {
198a9db528eSjruoho struct acpi_devnode *ad = acpi_match_node(hdl);
1999ddf2c51Sjruoho struct acpi_power_res *res;
2009ddf2c51Sjruoho
20120525938Sjruoho if (ad == NULL)
20220525938Sjruoho return;
2039ddf2c51Sjruoho
2049ddf2c51Sjruoho /*
2059ddf2c51Sjruoho * Remove all references in each resource.
2069ddf2c51Sjruoho */
2079ddf2c51Sjruoho TAILQ_FOREACH(res, &res_head, res_list)
2089ddf2c51Sjruoho (void)acpi_power_res_deref(res, ad->ad_handle);
2099ddf2c51Sjruoho }
2109ddf2c51Sjruoho
2119ddf2c51Sjruoho /*
2129ddf2c51Sjruoho * Get the D-state of an ACPI device node.
2139ddf2c51Sjruoho */
2149ddf2c51Sjruoho bool
acpi_power_get(ACPI_HANDLE hdl,int * state)21520525938Sjruoho acpi_power_get(ACPI_HANDLE hdl, int *state)
2169ddf2c51Sjruoho {
217a9db528eSjruoho struct acpi_devnode *ad = acpi_match_node(hdl);
2189ddf2c51Sjruoho ACPI_STATUS rv;
2199ddf2c51Sjruoho
22020525938Sjruoho if (ad == NULL)
22120525938Sjruoho return false;
22220525938Sjruoho
223a24d1186Sjruoho /*
224ef2b1087Sjruoho * As _PSC may be broken, first try to
225ef2b1087Sjruoho * retrieve the power state indirectly
226ef2b1087Sjruoho * via power resources.
227a24d1186Sjruoho */
2289ddf2c51Sjruoho rv = acpi_power_get_indirect(ad);
2299ddf2c51Sjruoho
2309ddf2c51Sjruoho if (ACPI_FAILURE(rv))
231a24d1186Sjruoho rv = acpi_power_get_direct(ad);
232a24d1186Sjruoho
233a24d1186Sjruoho if (ACPI_FAILURE(rv))
2349ddf2c51Sjruoho goto fail;
2359ddf2c51Sjruoho
2369ddf2c51Sjruoho KASSERT(ad->ad_state != ACPI_STATE_ERROR);
2379ddf2c51Sjruoho
2389ddf2c51Sjruoho if (ad->ad_state < ACPI_STATE_D0 || ad->ad_state > ACPI_STATE_D3) {
2399ddf2c51Sjruoho rv = AE_BAD_VALUE;
2409ddf2c51Sjruoho goto fail;
2419ddf2c51Sjruoho }
2429ddf2c51Sjruoho
2439ddf2c51Sjruoho if (state != NULL)
2449ddf2c51Sjruoho *state = ad->ad_state;
2459ddf2c51Sjruoho
2469ddf2c51Sjruoho return true;
2479ddf2c51Sjruoho
2489ddf2c51Sjruoho fail:
2499ddf2c51Sjruoho ad->ad_state = ACPI_STATE_ERROR;
2509ddf2c51Sjruoho
2519ddf2c51Sjruoho if (state != NULL)
2529ddf2c51Sjruoho *state = ad->ad_state;
2539ddf2c51Sjruoho
254f640132dSjruoho ACPI_DEBUG_PRINT((ACPI_DB_INFO, "failed to get power state "
255f640132dSjruoho "for %s: %s\n", ad->ad_name, AcpiFormatException(rv)));
2569ddf2c51Sjruoho
2579ddf2c51Sjruoho return false;
2589ddf2c51Sjruoho }
2599ddf2c51Sjruoho
2609ddf2c51Sjruoho static ACPI_STATUS
acpi_power_get_direct(struct acpi_devnode * ad)2619ddf2c51Sjruoho acpi_power_get_direct(struct acpi_devnode *ad)
2629ddf2c51Sjruoho {
2639ddf2c51Sjruoho ACPI_INTEGER val = 0;
2649ddf2c51Sjruoho ACPI_STATUS rv;
2659ddf2c51Sjruoho
2669ddf2c51Sjruoho rv = acpi_eval_integer(ad->ad_handle, "_PSC", &val);
2679ddf2c51Sjruoho
2689ddf2c51Sjruoho KDASSERT((uint64_t)val < INT_MAX);
2699ddf2c51Sjruoho
2709ddf2c51Sjruoho ad->ad_state = (int)val;
2719ddf2c51Sjruoho
2729ddf2c51Sjruoho return rv;
2739ddf2c51Sjruoho }
2749ddf2c51Sjruoho
2759ddf2c51Sjruoho static ACPI_STATUS
acpi_power_get_indirect(struct acpi_devnode * ad)2769ddf2c51Sjruoho acpi_power_get_indirect(struct acpi_devnode *ad)
2779ddf2c51Sjruoho {
2789ddf2c51Sjruoho ACPI_OBJECT *pkg;
2799ddf2c51Sjruoho ACPI_STATUS rv;
2809ddf2c51Sjruoho int i;
2819ddf2c51Sjruoho
2829ddf2c51Sjruoho CTASSERT(ACPI_STATE_D0 == 0 && ACPI_STATE_D1 == 1);
2839ddf2c51Sjruoho CTASSERT(ACPI_STATE_D2 == 2 && ACPI_STATE_D3 == 3);
2849ddf2c51Sjruoho
2859ddf2c51Sjruoho /*
2869ddf2c51Sjruoho * The device is in a given D-state if all resources are on.
2879ddf2c51Sjruoho * To derive this, evaluate all elements in each _PRx package
2889ddf2c51Sjruoho * (x = 0 ... 3) and break if the noted condition becomes true.
2899ddf2c51Sjruoho */
2909ddf2c51Sjruoho for (ad->ad_state = ACPI_STATE_D3, i = 0; i < ACPI_STATE_D3; i++) {
2919ddf2c51Sjruoho
2929ddf2c51Sjruoho pkg = acpi_power_pkg_get(ad->ad_handle, i);
2939ddf2c51Sjruoho
2949ddf2c51Sjruoho if (pkg == NULL)
2959ddf2c51Sjruoho continue;
2969ddf2c51Sjruoho
2979ddf2c51Sjruoho /*
2989ddf2c51Sjruoho * For each element in the _PRx package, evaluate _STA
2999ddf2c51Sjruoho * and return AE_OK only if all power resources are on.
3009ddf2c51Sjruoho */
3019ddf2c51Sjruoho rv = acpi_foreach_package_object(pkg, acpi_power_res_sta, ad);
3029ddf2c51Sjruoho
303696f7c4bSjruoho if (ACPI_FAILURE(rv) && rv != AE_CTRL_FALSE)
3049ddf2c51Sjruoho goto out;
3059ddf2c51Sjruoho
3069ddf2c51Sjruoho if (ACPI_SUCCESS(rv)) {
3079ddf2c51Sjruoho ad->ad_state = i;
3089ddf2c51Sjruoho goto out;
3099ddf2c51Sjruoho }
3109ddf2c51Sjruoho
3119ddf2c51Sjruoho ACPI_FREE(pkg); pkg = NULL;
3129ddf2c51Sjruoho }
3139ddf2c51Sjruoho
3149ddf2c51Sjruoho KASSERT(ad->ad_state == ACPI_STATE_D3);
3159ddf2c51Sjruoho
3169ddf2c51Sjruoho return AE_OK;
3179ddf2c51Sjruoho
3189ddf2c51Sjruoho out:
3199ddf2c51Sjruoho ACPI_FREE(pkg);
3209ddf2c51Sjruoho
3219ddf2c51Sjruoho return rv;
3229ddf2c51Sjruoho }
3239ddf2c51Sjruoho
3249ddf2c51Sjruoho /*
3259ddf2c51Sjruoho * Set the D-state of an ACPI device node.
3269ddf2c51Sjruoho */
3279ddf2c51Sjruoho bool
acpi_power_set(ACPI_HANDLE hdl,int state)32820525938Sjruoho acpi_power_set(ACPI_HANDLE hdl, int state)
3299ddf2c51Sjruoho {
330a9db528eSjruoho struct acpi_devnode *ad = acpi_match_node(hdl);
3319ddf2c51Sjruoho ACPI_STATUS rv;
3329ddf2c51Sjruoho char path[5];
3339ddf2c51Sjruoho int old;
3349ddf2c51Sjruoho
33520525938Sjruoho if (ad == NULL)
33620525938Sjruoho return false;
3379ddf2c51Sjruoho
3385b0c1990Sjruoho if (state < ACPI_STATE_D0 || state > ACPI_STATE_D3) {
3395b0c1990Sjruoho rv = AE_BAD_PARAMETER;
3409ddf2c51Sjruoho goto fail;
3419ddf2c51Sjruoho }
3429ddf2c51Sjruoho
3439cb2ef6cSjruoho if (acpi_power_get(ad->ad_handle, &old) != true) {
3449ddf2c51Sjruoho rv = AE_NOT_FOUND;
3459ddf2c51Sjruoho goto fail;
3469ddf2c51Sjruoho }
3479ddf2c51Sjruoho
3489ddf2c51Sjruoho KASSERT(ad->ad_state == old);
3499ddf2c51Sjruoho KASSERT(ad->ad_state != ACPI_STATE_ERROR);
3509ddf2c51Sjruoho
3519ddf2c51Sjruoho if (ad->ad_state == state) {
3529ddf2c51Sjruoho rv = AE_ALREADY_EXISTS;
3539ddf2c51Sjruoho goto fail;
3549ddf2c51Sjruoho }
3559ddf2c51Sjruoho
3569ddf2c51Sjruoho /*
3579ddf2c51Sjruoho * It is only possible to go to D0 ("on") from D3 ("off").
3589ddf2c51Sjruoho */
3599ddf2c51Sjruoho if (ad->ad_state == ACPI_STATE_D3 && state != ACPI_STATE_D0) {
3609ddf2c51Sjruoho rv = AE_BAD_PARAMETER;
3619ddf2c51Sjruoho goto fail;
3629ddf2c51Sjruoho }
3639ddf2c51Sjruoho
3649ddf2c51Sjruoho /*
365fc9a58b3Sjruoho * As noted in ACPI 4.0 (appendix A.2.1), the bus power state
366fc9a58b3Sjruoho * should never be lower than the highest state of one of its
367fc9a58b3Sjruoho * devices. Consequently, we cannot set the state to a lower
368fc9a58b3Sjruoho * (i.e. higher power) state than the parent device's state.
369fc9a58b3Sjruoho */
37015a42e9fSjruoho if ((ad->ad_parent != NULL) &&
37115a42e9fSjruoho (ad->ad_parent->ad_flags & ACPI_DEVICE_POWER) != 0) {
37215a42e9fSjruoho
37315a42e9fSjruoho if (ad->ad_parent->ad_state > state) {
374fc9a58b3Sjruoho rv = AE_ABORT_METHOD;
375fc9a58b3Sjruoho goto fail;
376fc9a58b3Sjruoho }
37715a42e9fSjruoho }
378fc9a58b3Sjruoho
379fc9a58b3Sjruoho /*
380a24d1186Sjruoho * We first sweep through the resources required for the target
381a24d1186Sjruoho * state, turning things on and building references. After this
382a24d1186Sjruoho * we dereference the resources required for the current state,
3839ddf2c51Sjruoho * turning the resources off as we go.
3849ddf2c51Sjruoho */
3859ddf2c51Sjruoho rv = acpi_power_switch(ad, state, true);
3869ddf2c51Sjruoho
387a24d1186Sjruoho if (ACPI_FAILURE(rv) && rv != AE_CTRL_CONTINUE)
3889ddf2c51Sjruoho goto fail;
3899ddf2c51Sjruoho
3909ddf2c51Sjruoho rv = acpi_power_switch(ad, ad->ad_state, false);
3919ddf2c51Sjruoho
392a24d1186Sjruoho if (ACPI_FAILURE(rv) && rv != AE_CTRL_CONTINUE)
3939ddf2c51Sjruoho goto fail;
3949ddf2c51Sjruoho
3959ddf2c51Sjruoho /*
396a24d1186Sjruoho * Last but not least, invoke the power state switch method,
397a24d1186Sjruoho * if available. Because some systems use only _PSx for the
398a24d1186Sjruoho * power state transitions, we do this even if there is no _PRx.
3999ddf2c51Sjruoho */
4009ddf2c51Sjruoho (void)snprintf(path, sizeof(path), "_PS%d", state);
4019ddf2c51Sjruoho (void)AcpiEvaluateObject(ad->ad_handle, path, NULL, NULL);
4029ddf2c51Sjruoho
403fc9a58b3Sjruoho ACPI_DEBUG_PRINT((ACPI_DB_INFO, "%s turned from "
404fc9a58b3Sjruoho "D%d to D%d\n", ad->ad_name, old, state));
4059ddf2c51Sjruoho
406a24d1186Sjruoho ad->ad_state = state;
407a24d1186Sjruoho
4089ddf2c51Sjruoho return true;
4099ddf2c51Sjruoho
4109ddf2c51Sjruoho fail:
4119ddf2c51Sjruoho ad->ad_state = ACPI_STATE_ERROR;
4129ddf2c51Sjruoho
4138e7819f7Sjruoho ACPI_DEBUG_PRINT((ACPI_DB_INFO, "failed to set power state to D%d "
4148e7819f7Sjruoho "for %s: %s\n", state, ad->ad_name, AcpiFormatException(rv)));
4159ddf2c51Sjruoho
4167e3b451fSjruoho return false;
4179ddf2c51Sjruoho }
4189ddf2c51Sjruoho
4199ddf2c51Sjruoho static ACPI_STATUS
acpi_power_switch(struct acpi_devnode * ad,int state,bool on)4209ddf2c51Sjruoho acpi_power_switch(struct acpi_devnode *ad, int state, bool on)
4219ddf2c51Sjruoho {
4229c09c7fcSjruoho ACPI_OBJECT *elm, *pkg;
4239c09c7fcSjruoho ACPI_STATUS rv = AE_OK;
4249c09c7fcSjruoho ACPI_HANDLE hdl;
4259c09c7fcSjruoho uint32_t i, n;
4269ddf2c51Sjruoho
4279c09c7fcSjruoho /*
4289c09c7fcSjruoho * For each element in the _PRx package, fetch
4299c09c7fcSjruoho * the reference handle, search for this handle
4309c09c7fcSjruoho * from the power resource queue, and turn the
4319c09c7fcSjruoho * resource behind the handle on or off.
4329c09c7fcSjruoho */
4339ddf2c51Sjruoho pkg = acpi_power_pkg_get(ad->ad_handle, state);
4349ddf2c51Sjruoho
4359ddf2c51Sjruoho if (pkg == NULL)
436a24d1186Sjruoho return AE_CTRL_CONTINUE;
4379ddf2c51Sjruoho
4389c09c7fcSjruoho n = pkg->Package.Count;
4399ddf2c51Sjruoho
4409c09c7fcSjruoho for (i = 0; i < n; i++) {
4419c09c7fcSjruoho
4429c09c7fcSjruoho elm = &pkg->Package.Elements[i];
4439c09c7fcSjruoho rv = acpi_eval_reference_handle(elm, &hdl);
4449c09c7fcSjruoho
4459c09c7fcSjruoho if (ACPI_FAILURE(rv))
4469c09c7fcSjruoho continue;
4479c09c7fcSjruoho
4489c09c7fcSjruoho (void)acpi_power_res(hdl, ad->ad_handle, on);
4499c09c7fcSjruoho }
450a24d1186Sjruoho
4519ddf2c51Sjruoho ACPI_FREE(pkg);
4529ddf2c51Sjruoho
4539ddf2c51Sjruoho return rv;
4549ddf2c51Sjruoho }
4559ddf2c51Sjruoho
4569c09c7fcSjruoho ACPI_STATUS
acpi_power_res(ACPI_HANDLE hdl,ACPI_HANDLE ref,bool on)4579c09c7fcSjruoho acpi_power_res(ACPI_HANDLE hdl, ACPI_HANDLE ref, bool on)
4589ddf2c51Sjruoho {
4599ddf2c51Sjruoho struct acpi_power_res *res;
4609c09c7fcSjruoho const char *str;
4619ddf2c51Sjruoho ACPI_STATUS rv;
4629ddf2c51Sjruoho
4639ddf2c51Sjruoho /*
4649c09c7fcSjruoho * Search for the resource.
4659ddf2c51Sjruoho */
4669ddf2c51Sjruoho res = acpi_power_res_get(hdl);
4679ddf2c51Sjruoho
4689ddf2c51Sjruoho if (res == NULL)
4699ddf2c51Sjruoho return AE_NOT_FOUND;
4709ddf2c51Sjruoho
47143337e89Sjruoho if (ref == NULL)
47243337e89Sjruoho return AE_BAD_PARAMETER;
47343337e89Sjruoho
4749ddf2c51Sjruoho /*
47543337e89Sjruoho * Adjust the reference counting. This is
47643337e89Sjruoho * necessary since a single power resource
47743337e89Sjruoho * can be shared by multiple devices.
4789ddf2c51Sjruoho */
47956a181efSjoerg if (on) {
4809c09c7fcSjruoho rv = acpi_power_res_ref(res, ref);
4819c09c7fcSjruoho str = "_ON";
48256a181efSjoerg } else {
4839c09c7fcSjruoho rv = acpi_power_res_deref(res, ref);
4849c09c7fcSjruoho str = "_OFF";
4859ddf2c51Sjruoho }
4869ddf2c51Sjruoho
4879ddf2c51Sjruoho if (ACPI_FAILURE(rv))
4889ddf2c51Sjruoho return rv;
4899ddf2c51Sjruoho
4909c09c7fcSjruoho /*
4919c09c7fcSjruoho * Turn the resource on or off.
4929c09c7fcSjruoho */
4939c09c7fcSjruoho return AcpiEvaluateObject(res->res_handle, str, NULL, NULL);
4949ddf2c51Sjruoho }
4959ddf2c51Sjruoho
4969ddf2c51Sjruoho static ACPI_STATUS
acpi_power_res_ref(struct acpi_power_res * res,ACPI_HANDLE ref)49743337e89Sjruoho acpi_power_res_ref(struct acpi_power_res *res, ACPI_HANDLE ref)
4989ddf2c51Sjruoho {
49943337e89Sjruoho size_t i, j = SIZE_MAX;
5009ddf2c51Sjruoho
5019ddf2c51Sjruoho mutex_enter(&res->res_mutex);
5029ddf2c51Sjruoho
50343337e89Sjruoho for (i = 0; i < __arraycount(res->res_ref); i++) {
5049ddf2c51Sjruoho
50543337e89Sjruoho /*
50643337e89Sjruoho * Do not error out if the handle
50743337e89Sjruoho * has already been referenced.
50843337e89Sjruoho */
50943337e89Sjruoho if (res->res_ref[i] == ref) {
51043337e89Sjruoho mutex_exit(&res->res_mutex);
51143337e89Sjruoho return AE_OK;
5129ddf2c51Sjruoho }
5139ddf2c51Sjruoho
51443337e89Sjruoho if (j == SIZE_MAX && res->res_ref[i] == NULL)
51543337e89Sjruoho j = i;
51643337e89Sjruoho }
51743337e89Sjruoho
51843337e89Sjruoho if (j == SIZE_MAX) {
51943337e89Sjruoho mutex_exit(&res->res_mutex);
52043337e89Sjruoho return AE_LIMIT;
52143337e89Sjruoho }
52243337e89Sjruoho
52343337e89Sjruoho res->res_ref[j] = ref;
5249ddf2c51Sjruoho mutex_exit(&res->res_mutex);
5259ddf2c51Sjruoho
526a84baa02Sjruoho ACPI_DEBUG_PRINT((ACPI_DB_INFO, "%s referenced "
52743337e89Sjruoho "by %s\n", res->res_name, acpi_xname(ref)));
5289ddf2c51Sjruoho
5299ddf2c51Sjruoho return AE_OK;
5309ddf2c51Sjruoho }
5319ddf2c51Sjruoho
5329ddf2c51Sjruoho static ACPI_STATUS
acpi_power_res_deref(struct acpi_power_res * res,ACPI_HANDLE ref)53343337e89Sjruoho acpi_power_res_deref(struct acpi_power_res *res, ACPI_HANDLE ref)
5349ddf2c51Sjruoho {
53543337e89Sjruoho size_t i;
5369ddf2c51Sjruoho
5379ddf2c51Sjruoho mutex_enter(&res->res_mutex);
5389ddf2c51Sjruoho
53943337e89Sjruoho for (i = 0; i < __arraycount(res->res_ref); i++) {
5409ddf2c51Sjruoho
54143337e89Sjruoho if (res->res_ref[i] != ref)
54243337e89Sjruoho continue;
5439ddf2c51Sjruoho
54443337e89Sjruoho res->res_ref[i] = NULL;
5459ddf2c51Sjruoho mutex_exit(&res->res_mutex);
5469ddf2c51Sjruoho
547a84baa02Sjruoho ACPI_DEBUG_PRINT((ACPI_DB_INFO, "%s dereferenced "
54843337e89Sjruoho "by %s\n", res->res_name, acpi_xname(ref)));
5499ddf2c51Sjruoho
5509ddf2c51Sjruoho return AE_OK;
5519ddf2c51Sjruoho }
5529ddf2c51Sjruoho
55343337e89Sjruoho /*
55443337e89Sjruoho * If the array remains to be non-empty,
55543337e89Sjruoho * something else is using the resource
55643337e89Sjruoho * and hence it can not be turned off.
55743337e89Sjruoho */
55843337e89Sjruoho mutex_exit(&res->res_mutex);
55943337e89Sjruoho
56043337e89Sjruoho return AE_ABORT_METHOD;
56143337e89Sjruoho }
56243337e89Sjruoho
5639ddf2c51Sjruoho static ACPI_STATUS
acpi_power_res_sta(ACPI_OBJECT * elm,void * arg)5649ddf2c51Sjruoho acpi_power_res_sta(ACPI_OBJECT *elm, void *arg)
5659ddf2c51Sjruoho {
5669ddf2c51Sjruoho ACPI_INTEGER val;
5679ddf2c51Sjruoho ACPI_HANDLE hdl;
5689ddf2c51Sjruoho ACPI_STATUS rv;
5699ddf2c51Sjruoho
5709ddf2c51Sjruoho rv = acpi_eval_reference_handle(elm, &hdl);
5719ddf2c51Sjruoho
5729ddf2c51Sjruoho if (ACPI_FAILURE(rv))
5739ddf2c51Sjruoho goto fail;
5749ddf2c51Sjruoho
5759ddf2c51Sjruoho rv = acpi_eval_integer(hdl, "_STA", &val);
5769ddf2c51Sjruoho
5779ddf2c51Sjruoho if (ACPI_FAILURE(rv))
5789ddf2c51Sjruoho goto fail;
5799ddf2c51Sjruoho
5809ddf2c51Sjruoho KDASSERT((uint64_t)val < INT_MAX);
5819ddf2c51Sjruoho
5829ddf2c51Sjruoho if ((int)val != ACPI_STA_POW_ON && (int)val != ACPI_STA_POW_OFF)
5839ddf2c51Sjruoho return AE_BAD_VALUE;
5849ddf2c51Sjruoho
5859ddf2c51Sjruoho if ((int)val != ACPI_STA_POW_ON)
586696f7c4bSjruoho return AE_CTRL_FALSE; /* XXX: Not an error. */
5879ddf2c51Sjruoho
5889ddf2c51Sjruoho return AE_OK;
5899ddf2c51Sjruoho
5909ddf2c51Sjruoho fail:
591f655bf2aSjruoho if (rv == AE_CTRL_FALSE)
5929ddf2c51Sjruoho rv = AE_ERROR;
5939ddf2c51Sjruoho
594a84baa02Sjruoho ACPI_DEBUG_PRINT((ACPI_DB_DEBUG_OBJECT, "failed to evaluate _STA "
595a84baa02Sjruoho "for %s: %s\n", acpi_xname(hdl), AcpiFormatException(rv)));
5969ddf2c51Sjruoho
5979ddf2c51Sjruoho return rv;
5989ddf2c51Sjruoho }
5999ddf2c51Sjruoho
6009ddf2c51Sjruoho static ACPI_OBJECT *
acpi_power_pkg_get(ACPI_HANDLE hdl,int state)6019ddf2c51Sjruoho acpi_power_pkg_get(ACPI_HANDLE hdl, int state)
6029ddf2c51Sjruoho {
6039ddf2c51Sjruoho char path[5] = "_PR?";
6049ddf2c51Sjruoho ACPI_OBJECT *obj;
6059ddf2c51Sjruoho ACPI_BUFFER buf;
6069ddf2c51Sjruoho ACPI_STATUS rv;
6079ddf2c51Sjruoho
6089ddf2c51Sjruoho path[3] = '0' + state;
6099ddf2c51Sjruoho
6109ddf2c51Sjruoho rv = acpi_eval_struct(hdl, path, &buf);
6119ddf2c51Sjruoho
6129ddf2c51Sjruoho if (ACPI_FAILURE(rv))
6139ddf2c51Sjruoho goto fail;
6149ddf2c51Sjruoho
6159ddf2c51Sjruoho if (buf.Length == 0) {
6169ddf2c51Sjruoho rv = AE_LIMIT;
6179ddf2c51Sjruoho goto fail;
6189ddf2c51Sjruoho }
6199ddf2c51Sjruoho
6209ddf2c51Sjruoho obj = buf.Pointer;
6219ddf2c51Sjruoho
6229ddf2c51Sjruoho if (obj->Type != ACPI_TYPE_PACKAGE) {
6239ddf2c51Sjruoho rv = AE_TYPE;
6249ddf2c51Sjruoho goto fail;
6259ddf2c51Sjruoho }
6269ddf2c51Sjruoho
6279ddf2c51Sjruoho if (obj->Package.Count == 0) {
6289ddf2c51Sjruoho rv = AE_LIMIT;
6299ddf2c51Sjruoho goto fail;
6309ddf2c51Sjruoho }
6319ddf2c51Sjruoho
6329ddf2c51Sjruoho return obj;
6339ddf2c51Sjruoho
6349ddf2c51Sjruoho fail:
6359ddf2c51Sjruoho if (buf.Pointer != NULL)
6369ddf2c51Sjruoho ACPI_FREE(buf.Pointer);
6379ddf2c51Sjruoho
638a84baa02Sjruoho ACPI_DEBUG_PRINT((ACPI_DB_DEBUG_OBJECT, "failed to evaluate %s for "
639a84baa02Sjruoho "%s: %s\n", path, acpi_xname(hdl), AcpiFormatException(rv)));
6409ddf2c51Sjruoho
6419ddf2c51Sjruoho return NULL;
6429ddf2c51Sjruoho }
6439ddf2c51Sjruoho
64416bdc3ffSjruoho SYSCTL_SETUP(sysctl_acpi_power_setup, "sysctl hw.acpi.power subtree setup")
64516bdc3ffSjruoho {
646646cb7e2Sgsutre const struct sysctlnode *anode;
64716bdc3ffSjruoho int err;
64816bdc3ffSjruoho
64916bdc3ffSjruoho err = sysctl_createv(NULL, 0, NULL, &anode,
65016bdc3ffSjruoho CTLFLAG_PERMANENT, CTLTYPE_NODE, "acpi",
65116bdc3ffSjruoho NULL, NULL, 0, NULL, 0,
6524f6fb3bfSpooka CTL_HW, CTL_CREATE, CTL_EOL);
65316bdc3ffSjruoho
65416bdc3ffSjruoho if (err != 0)
655646cb7e2Sgsutre return;
656646cb7e2Sgsutre
657646cb7e2Sgsutre acpi_power_acpinode = anode->sysctl_num;
65816bdc3ffSjruoho
65916bdc3ffSjruoho err = sysctl_createv(NULL, 0, &anode, &anode,
66016bdc3ffSjruoho CTLFLAG_PERMANENT, CTLTYPE_NODE,
66116bdc3ffSjruoho "power", SYSCTL_DESCR("ACPI device power states"),
66216bdc3ffSjruoho NULL, 0, NULL, 0,
66316bdc3ffSjruoho CTL_CREATE, CTL_EOL);
66416bdc3ffSjruoho
66516bdc3ffSjruoho if (err != 0)
66616bdc3ffSjruoho return;
66716bdc3ffSjruoho
668646cb7e2Sgsutre acpi_power_powernode = anode->sysctl_num;
66916bdc3ffSjruoho }
67016bdc3ffSjruoho
67116bdc3ffSjruoho void
acpi_power_add(struct acpi_devnode * ad)67216bdc3ffSjruoho acpi_power_add(struct acpi_devnode *ad)
67316bdc3ffSjruoho {
674e008cf16Sjruoho const char *str = NULL;
67516bdc3ffSjruoho int err;
67616bdc3ffSjruoho
67716bdc3ffSjruoho KASSERT(ad != NULL && ad->ad_root != NULL);
67816bdc3ffSjruoho KASSERT((ad->ad_flags & ACPI_DEVICE_POWER) != 0);
67916bdc3ffSjruoho
680646cb7e2Sgsutre if (acpi_power_acpinode == CTL_EOL ||
681646cb7e2Sgsutre acpi_power_powernode == CTL_EOL)
68216bdc3ffSjruoho return;
68316bdc3ffSjruoho
684e008cf16Sjruoho if (ad->ad_device != NULL)
685e008cf16Sjruoho str = device_xname(ad->ad_device);
686*ef019da3Smrg #if NPCI > 0
687e008cf16Sjruoho else {
688*ef019da3Smrg device_t dev = acpi_pcidev_find_dev(ad);
689e008cf16Sjruoho
690e008cf16Sjruoho if (dev != NULL)
691e008cf16Sjruoho str = device_xname(dev);
692e008cf16Sjruoho }
693*ef019da3Smrg #endif
694e008cf16Sjruoho
695e008cf16Sjruoho if (str == NULL)
696e008cf16Sjruoho return;
697e008cf16Sjruoho
698646cb7e2Sgsutre err = sysctl_createv(NULL, 0, NULL, NULL,
699e008cf16Sjruoho CTLFLAG_READONLY, CTLTYPE_STRING, str,
700e21a34c2Sdsl NULL, acpi_power_sysctl, 0, (void *)ad, 0, CTL_HW,
701e008cf16Sjruoho acpi_power_acpinode, acpi_power_powernode,
70216bdc3ffSjruoho CTL_CREATE, CTL_EOL);
70316bdc3ffSjruoho
70416bdc3ffSjruoho if (err != 0)
70516bdc3ffSjruoho aprint_error_dev(ad->ad_root, "sysctl_createv"
7066248a51dSjruoho "(hw.acpi.power.%s) failed (err %d)\n", str, err);
70716bdc3ffSjruoho }
70816bdc3ffSjruoho
70916bdc3ffSjruoho static int
acpi_power_sysctl(SYSCTLFN_ARGS)71016bdc3ffSjruoho acpi_power_sysctl(SYSCTLFN_ARGS)
71116bdc3ffSjruoho {
712ba4f1cd0Sjruoho struct acpi_devnode *ad;
71316bdc3ffSjruoho struct sysctlnode node;
71416bdc3ffSjruoho int err, state;
71516bdc3ffSjruoho char t[3];
71616bdc3ffSjruoho
71716bdc3ffSjruoho node = *rnode;
718ba4f1cd0Sjruoho ad = rnode->sysctl_data;
719ba4f1cd0Sjruoho
7209cb2ef6cSjruoho if (acpi_power_get(ad->ad_handle, &state) != true)
721ba4f1cd0Sjruoho state = 0;
72216bdc3ffSjruoho
72316bdc3ffSjruoho (void)memset(t, '\0', sizeof(t));
72416bdc3ffSjruoho (void)snprintf(t, sizeof(t), "D%d", state);
72516bdc3ffSjruoho
72616bdc3ffSjruoho node.sysctl_data = &t;
72716bdc3ffSjruoho
72816bdc3ffSjruoho err = sysctl_lookup(SYSCTLFN_CALL(&node));
72916bdc3ffSjruoho
73016bdc3ffSjruoho if (err || newp == NULL)
73116bdc3ffSjruoho return err;
73216bdc3ffSjruoho
73316bdc3ffSjruoho return 0;
73416bdc3ffSjruoho }
73516bdc3ffSjruoho
7369ddf2c51Sjruoho /*
7379ddf2c51Sjruoho * XXX: Move this to acpi_util.c by refactoring
7389ddf2c51Sjruoho * acpi_name() to optionally return a single name.
7399ddf2c51Sjruoho */
7409ddf2c51Sjruoho static const char *
acpi_xname(ACPI_HANDLE hdl)7419ddf2c51Sjruoho acpi_xname(ACPI_HANDLE hdl)
7429ddf2c51Sjruoho {
7439ddf2c51Sjruoho static char str[5];
7449ddf2c51Sjruoho ACPI_BUFFER buf;
7459ddf2c51Sjruoho ACPI_STATUS rv;
7469ddf2c51Sjruoho
7479ddf2c51Sjruoho buf.Pointer = str;
7489ddf2c51Sjruoho buf.Length = sizeof(str);
7499ddf2c51Sjruoho
7509ddf2c51Sjruoho rv = AcpiGetName(hdl, ACPI_SINGLE_NAME, &buf);
7519ddf2c51Sjruoho
7529ddf2c51Sjruoho if (ACPI_FAILURE(rv))
7539ddf2c51Sjruoho return "????";
7549ddf2c51Sjruoho
7559ddf2c51Sjruoho return str;
7569ddf2c51Sjruoho }
757