1 /* $NetBSD: acpi_wakedev.c,v 1.30 2024/12/09 23:41:36 jmcneill Exp $ */ 2 3 /*- 4 * Copyright (c) 2009, 2010, 2011 Jared D. McNeill <jmcneill@invisible.ca> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include <sys/cdefs.h> 30 __KERNEL_RCSID(0, "$NetBSD: acpi_wakedev.c,v 1.30 2024/12/09 23:41:36 jmcneill Exp $"); 31 32 #include "pci.h" 33 34 #include <sys/param.h> 35 #include <sys/device.h> 36 #include <sys/kmem.h> 37 #include <sys/sysctl.h> 38 39 #include <dev/acpi/acpireg.h> 40 #include <dev/acpi/acpivar.h> 41 #include <dev/acpi/acpi_pci.h> 42 #include <dev/acpi/acpi_power.h> 43 #include <dev/acpi/acpi_wakedev.h> 44 45 #define _COMPONENT ACPI_BUS_COMPONENT 46 ACPI_MODULE_NAME ("acpi_wakedev") 47 48 static const char * const acpi_wakedev_default[] = { 49 "PNP0C0C", /* power button */ 50 "PNP0C0E", /* sleep button */ 51 "PNP0C0D", /* lid switch */ 52 "PNP03??", /* PC KBD port */ 53 NULL, 54 }; 55 56 static int32_t acpi_wakedev_acpinode = CTL_EOL; 57 static int32_t acpi_wakedev_wakenode = CTL_EOL; 58 59 static void acpi_wakedev_power_add(struct acpi_devnode *, ACPI_OBJECT *); 60 static void acpi_wakedev_power_set(struct acpi_devnode *, bool); 61 static void acpi_wakedev_method(struct acpi_devnode *, int); 62 63 void 64 acpi_wakedev_init(struct acpi_devnode *ad) 65 { 66 ACPI_OBJECT *elm, *obj; 67 ACPI_INTEGER val; 68 ACPI_HANDLE hdl; 69 ACPI_BUFFER buf; 70 ACPI_STATUS rv; 71 72 KASSERT(ad != NULL && ad->ad_wakedev == NULL); 73 KASSERT(ad->ad_devinfo->Type == ACPI_TYPE_DEVICE); 74 75 rv = acpi_eval_struct(ad->ad_handle, "_PRW", &buf); 76 77 if (ACPI_FAILURE(rv)) 78 goto out; 79 80 obj = buf.Pointer; 81 82 if (obj->Type != ACPI_TYPE_PACKAGE) { 83 rv = AE_TYPE; 84 goto out; 85 } 86 87 if (obj->Package.Count < 2 || obj->Package.Count > UINT32_MAX) { 88 rv = AE_LIMIT; 89 goto out; 90 } 91 92 /* 93 * As noted in ACPI 3.0 (section 7.2.10), the _PRW object is 94 * a package in which the first element is either an integer 95 * or again a package. In the latter case the package inside 96 * the package element has two elements, a reference handle 97 * and the GPE number. 98 */ 99 elm = &obj->Package.Elements[0]; 100 101 switch (elm->Type) { 102 103 case ACPI_TYPE_INTEGER: 104 val = elm->Integer.Value; 105 hdl = NULL; 106 break; 107 108 case ACPI_TYPE_PACKAGE: 109 110 if (elm->Package.Count < 2) { 111 rv = AE_LIMIT; 112 goto out; 113 } 114 115 rv = AE_TYPE; 116 117 if (elm->Package.Elements[0].Type != ACPI_TYPE_LOCAL_REFERENCE) 118 goto out; 119 120 if (elm->Package.Elements[1].Type != ACPI_TYPE_INTEGER) 121 goto out; 122 123 hdl = elm->Package.Elements[0].Reference.Handle; 124 val = elm->Package.Elements[1].Integer.Value; 125 break; 126 127 default: 128 rv = AE_TYPE; 129 goto out; 130 } 131 132 ad->ad_wakedev = kmem_zalloc(sizeof(*ad->ad_wakedev), KM_SLEEP); 133 ad->ad_wakedev->aw_handle = hdl; 134 ad->ad_wakedev->aw_number = val; 135 136 /* 137 * The second element in _PRW is an integer 138 * that contains the lowest sleep state that 139 * can be entered while still providing wakeup. 140 */ 141 elm = &obj->Package.Elements[1]; 142 143 if (elm->Type == ACPI_TYPE_INTEGER) 144 ad->ad_wakedev->aw_state = elm->Integer.Value; 145 146 /* 147 * The rest of the elements are reference 148 * handles to power resources. Store these. 149 */ 150 acpi_wakedev_power_add(ad, obj); 151 152 /* 153 * Last but not least, mark the GPE for wake. 154 */ 155 if (!AcpiGbl_ReducedHardware) { 156 rv = AcpiSetupGpeForWake(ad->ad_handle, hdl, val); 157 } else { 158 rv = AE_OK; 159 } 160 161 out: 162 if (buf.Pointer != NULL) 163 ACPI_FREE(buf.Pointer); 164 165 if (ACPI_FAILURE(rv) && rv != AE_NOT_FOUND) 166 aprint_error_dev(ad->ad_root, "failed to evaluate _PRW " 167 "for %s: %s\n", ad->ad_name, AcpiFormatException(rv)); 168 } 169 170 static void 171 acpi_wakedev_power_add(struct acpi_devnode *ad, ACPI_OBJECT *obj) 172 { 173 struct acpi_wakedev *aw = ad->ad_wakedev; 174 uint32_t i, j, n; 175 ACPI_OBJECT *elm; 176 ACPI_HANDLE hdl; 177 ACPI_STATUS rv; 178 179 for (i = 0; i < __arraycount(aw->aw_power); i++) 180 aw->aw_power[i] = NULL; 181 182 n = obj->Package.Count; 183 184 if (n < 3 || n - 2 > __arraycount(aw->aw_power)) 185 return; 186 187 for (i = 2, j = 0; i < n; i++, j++) { 188 189 elm = &obj->Package.Elements[i]; 190 rv = acpi_eval_reference_handle(elm, &hdl); 191 192 if (ACPI_FAILURE(rv)) 193 continue; 194 195 ad->ad_wakedev->aw_power[j] = hdl; 196 } 197 } 198 199 static void 200 acpi_wakedev_power_set(struct acpi_devnode *ad, bool enable) 201 { 202 struct acpi_wakedev *aw = ad->ad_wakedev; 203 uint8_t i; 204 205 for (i = 0; i < __arraycount(aw->aw_power); i++) { 206 207 if (aw->aw_power[i] == NULL) 208 continue; 209 210 (void)acpi_power_res(aw->aw_power[i], ad->ad_handle, enable); 211 } 212 } 213 214 void 215 acpi_wakedev_add(struct acpi_devnode *ad) 216 { 217 struct acpi_wakedev *aw; 218 const char *str = NULL; 219 int err; 220 221 KASSERT(ad != NULL && ad->ad_wakedev != NULL); 222 KASSERT((ad->ad_flags & ACPI_DEVICE_WAKEUP) != 0); 223 224 aw = ad->ad_wakedev; 225 aw->aw_enable = false; 226 227 if (acpi_match_hid(ad->ad_devinfo, acpi_wakedev_default)) 228 aw->aw_enable = true; 229 230 if (acpi_wakedev_acpinode == CTL_EOL || 231 acpi_wakedev_wakenode == CTL_EOL) 232 return; 233 234 if (ad->ad_device != NULL) 235 str = device_xname(ad->ad_device); 236 #if NPCI > 0 237 else { 238 device_t dev = acpi_pcidev_find_dev(ad); 239 240 if (dev != NULL) 241 str = device_xname(dev); 242 } 243 #endif 244 245 if (str == NULL) 246 return; 247 248 err = sysctl_createv(NULL, 0, NULL, NULL, 249 CTLFLAG_READWRITE, CTLTYPE_BOOL, str, 250 NULL, NULL, 0, &aw->aw_enable, 0, CTL_HW, 251 acpi_wakedev_acpinode, acpi_wakedev_wakenode, 252 CTL_CREATE, CTL_EOL); 253 254 if (err != 0) 255 aprint_error_dev(ad->ad_root, "sysctl_createv" 256 "(hw.acpi.wake.%s) failed (err %d)\n", str, err); 257 } 258 259 SYSCTL_SETUP(sysctl_acpi_wakedev_setup, "sysctl hw.acpi.wake subtree setup") 260 { 261 const struct sysctlnode *rnode; 262 int err; 263 264 err = sysctl_createv(NULL, 0, NULL, &rnode, 265 CTLFLAG_PERMANENT, CTLTYPE_NODE, "acpi", 266 NULL, NULL, 0, NULL, 0, 267 CTL_HW, CTL_CREATE, CTL_EOL); 268 269 if (err != 0) 270 return; 271 272 acpi_wakedev_acpinode = rnode->sysctl_num; 273 274 err = sysctl_createv(NULL, 0, &rnode, &rnode, 275 CTLFLAG_PERMANENT, CTLTYPE_NODE, 276 "wake", SYSCTL_DESCR("ACPI device wake-up"), 277 NULL, 0, NULL, 0, 278 CTL_CREATE, CTL_EOL); 279 280 if (err != 0) 281 return; 282 283 acpi_wakedev_wakenode = rnode->sysctl_num; 284 } 285 286 void 287 acpi_wakedev_commit(struct acpi_softc *sc, int state) 288 { 289 struct acpi_devnode *ad; 290 ACPI_INTEGER val; 291 ACPI_HANDLE hdl; 292 293 /* 294 * To prepare a device for wakeup: 295 * 296 * 1. Set the wake GPE. 297 * 298 * 2. Turn on power resources. 299 * 300 * 3. Execute _DSW or _PSW method. 301 */ 302 SIMPLEQ_FOREACH(ad, &sc->sc_head, ad_list) { 303 304 if (ad->ad_wakedev == NULL) 305 continue; 306 307 if (state > ad->ad_wakedev->aw_state) 308 continue; 309 310 hdl = ad->ad_wakedev->aw_handle; 311 val = ad->ad_wakedev->aw_number; 312 313 if (state == ACPI_STATE_S0) { 314 if (!AcpiGbl_ReducedHardware) { 315 AcpiSetGpeWakeMask(hdl, val, ACPI_GPE_DISABLE); 316 } 317 continue; 318 } 319 320 if (!AcpiGbl_ReducedHardware) { 321 AcpiSetGpeWakeMask(hdl, val, ACPI_GPE_ENABLE); 322 } 323 324 acpi_wakedev_power_set(ad, true); 325 acpi_wakedev_method(ad, state); 326 } 327 } 328 329 static void 330 acpi_wakedev_method(struct acpi_devnode *ad, int state) 331 { 332 const bool enable = ad->ad_wakedev->aw_enable; 333 ACPI_OBJECT_LIST arg; 334 ACPI_OBJECT obj[3]; 335 ACPI_STATUS rv; 336 337 /* 338 * First try to call the Device Sleep Wake control method, _DSW. 339 * Only if this is not available, resort to to the Power State 340 * Wake control method, _PSW, which was deprecated in ACPI 3.0. 341 * 342 * The arguments to these methods are as follows: 343 * 344 * arg0 arg1 arg2 345 * ---- ---- ---- 346 * _PSW 0: disable 347 * 1: enable 348 * 349 * _DSW 0: disable 0: S0 0: D0 350 * 1: enable 1: S1 1: D0 or D1 351 * 2: D0, D1, or D2 352 * x: Sx 3: D0, D1, D2 or D3 353 */ 354 arg.Count = 3; 355 arg.Pointer = obj; 356 357 obj[0].Integer.Value = enable; 358 obj[1].Integer.Value = state; 359 obj[2].Integer.Value = ACPI_STATE_D0; 360 361 obj[0].Type = obj[1].Type = obj[2].Type = ACPI_TYPE_INTEGER; 362 363 rv = AcpiEvaluateObject(ad->ad_handle, "_DSW", &arg, NULL); 364 365 if (ACPI_SUCCESS(rv)) 366 return; 367 368 if (rv != AE_NOT_FOUND) 369 goto fail; 370 371 rv = acpi_eval_set_integer(ad->ad_handle, "_PSW", enable); 372 373 if (ACPI_FAILURE(rv) && rv != AE_NOT_FOUND) 374 goto fail; 375 376 return; 377 378 fail: 379 aprint_error_dev(ad->ad_root, "failed to evaluate wake " 380 "control method: %s\n", AcpiFormatException(rv)); 381 } 382