1 /* $NetBSD: acpi_apm.c,v 1.12 2007/12/09 20:27:52 jmcneill Exp $ */ 2 3 /*- 4 * Copyright (c) 2006 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Christos Zoulas and by Jared McNeill. 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 * 3. All advertising materials mentioning features or use of this software 19 * must display the following acknowledgement: 20 * This product includes software developed by the NetBSD 21 * Foundation, Inc. and its contributors. 22 * 4. Neither the name of The NetBSD Foundation nor the names of its 23 * contributors may be used to endorse or promote products derived 24 * from this software without specific prior written permission. 25 * 26 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 27 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 28 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 29 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 30 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 32 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 33 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 36 * POSSIBILITY OF SUCH DAMAGE. 37 */ 38 39 /* 40 * Autoconfiguration support for the Intel ACPI Component Architecture 41 * ACPI reference implementation. 42 */ 43 44 #include <sys/cdefs.h> 45 __KERNEL_RCSID(0, "$NetBSD: acpi_apm.c,v 1.12 2007/12/09 20:27:52 jmcneill Exp $"); 46 47 #include <sys/param.h> 48 #include <sys/systm.h> 49 #include <sys/device.h> 50 #include <sys/malloc.h> 51 #include <sys/kernel.h> 52 #include <sys/proc.h> 53 #include <sys/sysctl.h> 54 #include <sys/select.h> 55 #include <sys/envsys.h> 56 #include <dev/sysmon/sysmonvar.h> 57 58 #include <dev/acpi/acpica.h> 59 #include <dev/apm/apmvar.h> 60 61 static void acpiapm_disconnect(void *); 62 static void acpiapm_enable(void *, int); 63 static int acpiapm_set_powstate(void *, u_int, u_int); 64 static int acpiapm_get_powstat(void *, u_int, struct apm_power_info *); 65 static int acpiapm_get_event(void *, u_int *, u_int *); 66 static void acpiapm_cpu_busy(void *); 67 static void acpiapm_cpu_idle(void *); 68 static void acpiapm_get_capabilities(void *, u_int *, u_int *); 69 70 struct apm_accessops acpiapm_accessops = { 71 acpiapm_disconnect, 72 acpiapm_enable, 73 acpiapm_set_powstate, 74 acpiapm_get_powstat, 75 acpiapm_get_event, 76 acpiapm_cpu_busy, 77 acpiapm_cpu_idle, 78 acpiapm_get_capabilities, 79 }; 80 81 #ifdef ACPI_APM_DEBUG 82 #define DPRINTF(a) uprintf a 83 #else 84 #define DPRINTF(a) 85 #endif 86 87 #ifndef ACPI_APM_DEFAULT_STANDBY_STATE 88 #define ACPI_APM_DEFAULT_STANDBY_STATE (1) 89 #endif 90 #ifndef ACPI_APM_DEFAULT_SUSPEND_STATE 91 #define ACPI_APM_DEFAULT_SUSPEND_STATE (3) 92 #endif 93 #define ACPI_APM_DEFAULT_CAP \ 94 ((ACPI_APM_DEFAULT_STANDBY_STATE!=0 ? APM_GLOBAL_STANDBY : 0) | \ 95 (ACPI_APM_DEFAULT_SUSPEND_STATE!=0 ? APM_GLOBAL_SUSPEND : 0)) 96 #define ACPI_APM_STATE_MIN (0) 97 #define ACPI_APM_STATE_MAX (4) 98 99 /* It is assumed that there is only acpiapm instance. */ 100 static int resumed = 0, capability_changed = 0; 101 static int standby_state = ACPI_APM_DEFAULT_STANDBY_STATE; 102 static int suspend_state = ACPI_APM_DEFAULT_SUSPEND_STATE; 103 static int capabilities = ACPI_APM_DEFAULT_CAP; 104 static int acpiapm_node = CTL_EOL, standby_node = CTL_EOL; 105 106 struct acpi_softc; 107 extern ACPI_STATUS acpi_enter_sleep_state(struct acpi_softc *, int); 108 static int acpiapm_match(struct device *, struct cfdata *, void *); 109 static void acpiapm_attach(struct device *, struct device *, void *); 110 static int sysctl_state(SYSCTLFN_PROTO); 111 112 CFATTACH_DECL(acpiapm, sizeof(struct apm_softc), 113 acpiapm_match, acpiapm_attach, NULL, NULL); 114 115 static int 116 /*ARGSUSED*/ 117 acpiapm_match(struct device *parent, 118 struct cfdata *match, void *aux) 119 { 120 return apm_match(); 121 } 122 123 static void 124 /*ARGSUSED*/ 125 acpiapm_attach(struct device *parent, struct device *self, void *aux) 126 { 127 struct apm_softc *sc = (struct apm_softc *)self; 128 129 sc->sc_ops = &acpiapm_accessops; 130 sc->sc_cookie = parent; 131 sc->sc_vers = 0x0102; 132 sc->sc_detail = 0; 133 sc->sc_hwflags = APM_F_DONT_RUN_HOOKS; 134 apm_attach(sc); 135 } 136 137 static int 138 get_state_value(int id) 139 { 140 const int states[] = { 141 ACPI_STATE_S0, 142 ACPI_STATE_S1, 143 ACPI_STATE_S2, 144 ACPI_STATE_S3, 145 ACPI_STATE_S4 146 }; 147 148 if (id < ACPI_APM_STATE_MIN || id > ACPI_APM_STATE_MAX) 149 return ACPI_STATE_S0; 150 151 return states[id]; 152 } 153 154 static int 155 sysctl_state(SYSCTLFN_ARGS) 156 { 157 int newstate, error, *ref, cap, oldcap; 158 struct sysctlnode node; 159 160 if (rnode->sysctl_num == standby_node) { 161 ref = &standby_state; 162 cap = APM_GLOBAL_STANDBY; 163 } else { 164 ref = &suspend_state; 165 cap = APM_GLOBAL_SUSPEND; 166 } 167 168 newstate = *ref; 169 node = *rnode; 170 node.sysctl_data = &newstate; 171 error = sysctl_lookup(SYSCTLFN_CALL(&node)); 172 if (error || newp == NULL) 173 return error; 174 175 if (newstate < ACPI_APM_STATE_MIN || newstate > ACPI_APM_STATE_MAX) 176 return EINVAL; 177 178 *ref = newstate; 179 oldcap = capabilities; 180 capabilities = newstate != 0 ? oldcap | cap : oldcap & ~cap; 181 if ((capabilities ^ oldcap) != 0) 182 capability_changed = 1; 183 184 return 0; 185 } 186 187 SYSCTL_SETUP(sysctl_acpiapm_setup, "sysctl machdep.acpiapm subtree setup") 188 { 189 const struct sysctlnode *node; 190 191 if (sysctl_createv(clog, 0, NULL, NULL, 192 CTLFLAG_PERMANENT, 193 CTLTYPE_NODE, "machdep", NULL, 194 NULL, 0, NULL, 0, CTL_MACHDEP, CTL_EOL)) 195 return; 196 197 if (sysctl_createv(clog, 0, NULL, &node, 198 CTLFLAG_PERMANENT, 199 CTLTYPE_NODE, "acpiapm", NULL, 200 NULL, 0, NULL, 0, 201 CTL_MACHDEP, CTL_CREATE, CTL_EOL)) 202 return; 203 acpiapm_node = node->sysctl_num; 204 205 if (sysctl_createv(clog, 0, NULL, &node, 206 CTLFLAG_READWRITE, 207 CTLTYPE_INT, "standby", NULL, 208 &sysctl_state, 0, NULL, 0, 209 CTL_MACHDEP, acpiapm_node, CTL_CREATE, CTL_EOL)) 210 return; 211 standby_node = node->sysctl_num; 212 213 if (sysctl_createv(clog, 0, NULL, NULL, 214 CTLFLAG_READWRITE, 215 CTLTYPE_INT, "suspend", NULL, 216 &sysctl_state, 0, NULL, 0, 217 CTL_MACHDEP, acpiapm_node, CTL_CREATE, CTL_EOL)) 218 return; 219 } 220 221 /***************************************************************************** 222 * Minimalistic ACPI /dev/apm emulation support, for ACPI suspend 223 *****************************************************************************/ 224 225 static void 226 /*ARGSUSED*/ 227 acpiapm_disconnect(void *opaque) 228 { 229 return; 230 } 231 232 static void 233 /*ARGSUSED*/ 234 acpiapm_enable(void *opaque, int onoff) 235 { 236 return; 237 } 238 239 static int 240 acpiapm_set_powstate(void *opaque, u_int devid, u_int powstat) 241 { 242 struct acpi_softc *sc = opaque; 243 244 if (devid != APM_DEV_ALLDEVS) 245 return APM_ERR_UNRECOG_DEV; 246 247 switch (powstat) { 248 case APM_SYS_READY: 249 break; 250 case APM_SYS_STANDBY: 251 acpi_enter_sleep_state(sc, get_state_value(standby_state)); 252 resumed = 1; 253 break; 254 case APM_SYS_SUSPEND: 255 acpi_enter_sleep_state(sc, get_state_value(suspend_state)); 256 resumed = 1; 257 break; 258 case APM_SYS_OFF: 259 break; 260 case APM_LASTREQ_INPROG: 261 break; 262 case APM_LASTREQ_REJECTED: 263 break; 264 } 265 266 return 0; 267 } 268 269 static int 270 /*ARGSUSED*/ 271 acpiapm_get_powstat(void *opaque, u_int batteryid, 272 struct apm_power_info *pinfo) 273 { 274 #define APM_BATT_FLAG_WATERMARK_MASK (APM_BATT_FLAG_CRITICAL | \ 275 APM_BATT_FLAG_LOW | \ 276 APM_BATT_FLAG_HIGH) 277 int i, curcap, lowcap, warncap, cap, descap, lastcap, discharge; 278 int cap_valid, lastcap_valid, discharge_valid; 279 envsys_tre_data_t etds; 280 envsys_basic_info_t ebis; 281 282 /* Denote most variables as unitialized. */ 283 curcap = lowcap = warncap = descap = -1; 284 285 /* Prepare to aggregate these two variables over all batteries. */ 286 cap = lastcap = discharge = 0; 287 cap_valid = lastcap_valid = discharge_valid = 0; 288 289 (void)memset(pinfo, 0, sizeof(*pinfo)); 290 pinfo->ac_state = APM_AC_UNKNOWN; 291 pinfo->minutes_valid = 0; 292 pinfo->minutes_left = 0; 293 pinfo->batteryid = 0; 294 pinfo->nbattery = 0; /* to be incremented as batteries are found */ 295 pinfo->battery_flags = 0; 296 pinfo->battery_state = APM_BATT_UNKNOWN; /* ignored */ 297 pinfo->battery_life = APM_BATT_LIFE_UNKNOWN; 298 299 sysmonopen_envsys(0, 0, 0, &lwp0); 300 301 for (i = 0;; i++) { 302 const char *desc; 303 int data; 304 int flags; 305 306 ebis.sensor = i; 307 if (sysmonioctl_envsys(0, ENVSYS_GTREINFO, (void *)&ebis, 0, 308 NULL) || (ebis.validflags & ENVSYS_FVALID) == 0) 309 break; 310 etds.sensor = i; 311 if (sysmonioctl_envsys(0, ENVSYS_GTREDATA, (void *)&etds, 0, 312 NULL)) 313 continue; 314 desc = ebis.desc; 315 flags = etds.validflags; 316 data = etds.cur.data_s; 317 318 DPRINTF(("%d %s %d %d\n", i, desc, data, flags)); 319 if ((flags & ENVSYS_FCURVALID) == 0) 320 continue; 321 if (strstr(desc, " connected")) { 322 pinfo->ac_state = data ? APM_AC_ON : APM_AC_OFF; 323 } else if (strstr(desc, " present") && data == 0) 324 pinfo->battery_flags |= APM_BATT_FLAG_NO_SYSTEM_BATTERY; 325 else if (strstr(desc, " charging") && data) 326 pinfo->battery_flags |= APM_BATT_FLAG_CHARGING; 327 else if (strstr(desc, " charging") && !data) 328 pinfo->battery_flags &= ~APM_BATT_FLAG_CHARGING; 329 else if (strstr(desc, " warn cap")) 330 warncap = data / 1000; 331 else if (strstr(desc, " low cap")) 332 lowcap = data / 1000; 333 else if (strstr(desc, " last full cap")) { 334 lastcap += data / 1000; 335 lastcap_valid = 1; 336 } 337 else if (strstr(desc, " design cap")) 338 descap = data / 1000; 339 else if (strstr(desc, " charge") && 340 strstr(desc, " charge rate") == NULL && 341 strstr(desc, " charge state") == NULL) { 342 cap += data / 1000; 343 cap_valid = 1; 344 pinfo->nbattery++; 345 } 346 else if (strstr(desc, " discharge rate")) { 347 discharge += data / 1000; 348 discharge_valid = 1; 349 } 350 } 351 sysmonclose_envsys(0, 0, 0, &lwp0); 352 353 if (cap_valid > 0) { 354 if (warncap != -1 && cap < warncap) 355 pinfo->battery_flags |= APM_BATT_FLAG_CRITICAL; 356 else if (lowcap != -1) { 357 if (cap < lowcap) 358 pinfo->battery_flags |= APM_BATT_FLAG_LOW; 359 else 360 pinfo->battery_flags |= APM_BATT_FLAG_HIGH; 361 } 362 if (lastcap_valid > 0 && lastcap != 0) 363 pinfo->battery_life = 100 * cap / lastcap; 364 else if (descap != -1 && descap != 0) 365 pinfo->battery_life = 100 * cap / descap; 366 } 367 368 if ((pinfo->battery_flags & APM_BATT_FLAG_CHARGING) == 0) { 369 /* discharging */ 370 if (discharge != -1 && cap != -1 && discharge != 0) 371 pinfo->minutes_left = 60 * cap / discharge; 372 } 373 if ((pinfo->battery_flags & APM_BATT_FLAG_WATERMARK_MASK) == 0 && 374 (pinfo->battery_flags & APM_BATT_FLAG_NO_SYSTEM_BATTERY) == 0) { 375 if (pinfo->ac_state == APM_AC_ON) 376 pinfo->battery_flags |= APM_BATT_FLAG_HIGH; 377 else 378 pinfo->battery_flags |= APM_BATT_FLAG_LOW; 379 } 380 381 DPRINTF(("%d %d %d %d %d %d\n", cap, warncap, lowcap, lastcap, descap, 382 discharge)); 383 DPRINTF(("pinfo %d %d %d\n", pinfo->battery_flags, 384 pinfo->battery_life, pinfo->battery_life)); 385 return 0; 386 } 387 388 static int 389 /*ARGSUSED*/ 390 acpiapm_get_event(void *opaque, u_int *event_type, u_int *event_info) 391 { 392 if (capability_changed) { 393 capability_changed = 0; 394 *event_type = APM_CAP_CHANGE; 395 *event_info = 0; 396 return 0; 397 } 398 if (resumed) { 399 resumed = 0; 400 *event_type = APM_NORMAL_RESUME; 401 *event_info = 0; 402 return 0; 403 } 404 405 return APM_ERR_NOEVENTS; 406 } 407 408 static void 409 /*ARGSUSED*/ 410 acpiapm_cpu_busy(void *opaque) 411 { 412 return; 413 } 414 415 static void 416 /*ARGSUSED*/ 417 acpiapm_cpu_idle(void *opaque) 418 { 419 return; 420 } 421 422 static void 423 /*ARGSUSED*/ 424 acpiapm_get_capabilities(void *opaque, u_int *numbatts, 425 u_int *capflags) 426 { 427 *numbatts = 1; 428 *capflags = capabilities; 429 return; 430 } 431