1 /* $NetBSD: acpi_apm.c,v 1.19 2010/04/27 08:37:07 jruoho 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 * 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 * Autoconfiguration support for the Intel ACPI Component Architecture 34 * ACPI reference implementation. 35 */ 36 37 #include <sys/cdefs.h> 38 __KERNEL_RCSID(0, "$NetBSD: acpi_apm.c,v 1.19 2010/04/27 08:37:07 jruoho Exp $"); 39 40 #include <sys/param.h> 41 #include <sys/device.h> 42 #include <sys/sysctl.h> 43 #include <sys/systm.h> 44 #include <sys/queue.h> 45 #include <sys/envsys.h> 46 47 #include <dev/sysmon/sysmonvar.h> 48 49 #include <dev/acpi/acpivar.h> 50 #include <dev/apm/apmvar.h> 51 52 static void acpiapm_disconnect(void *); 53 static void acpiapm_enable(void *, int); 54 static int acpiapm_set_powstate(void *, u_int, u_int); 55 static int acpiapm_get_powstat(void *, u_int, struct apm_power_info *); 56 static bool apm_per_sensor(const struct sysmon_envsys *, 57 const envsys_data_t *, void *); 58 static int acpiapm_get_event(void *, u_int *, u_int *); 59 static void acpiapm_cpu_busy(void *); 60 static void acpiapm_cpu_idle(void *); 61 static void acpiapm_get_capabilities(void *, u_int *, u_int *); 62 63 struct apm_accessops acpiapm_accessops = { 64 acpiapm_disconnect, 65 acpiapm_enable, 66 acpiapm_set_powstate, 67 acpiapm_get_powstat, 68 acpiapm_get_event, 69 acpiapm_cpu_busy, 70 acpiapm_cpu_idle, 71 acpiapm_get_capabilities, 72 }; 73 74 #ifdef ACPI_APM_DEBUG 75 #define DPRINTF(a) uprintf a 76 #else 77 #define DPRINTF(a) 78 #endif 79 80 #ifndef ACPI_APM_DEFAULT_STANDBY_STATE 81 #define ACPI_APM_DEFAULT_STANDBY_STATE (1) 82 #endif 83 #ifndef ACPI_APM_DEFAULT_SUSPEND_STATE 84 #define ACPI_APM_DEFAULT_SUSPEND_STATE (3) 85 #endif 86 #define ACPI_APM_DEFAULT_CAP \ 87 ((ACPI_APM_DEFAULT_STANDBY_STATE!=0 ? APM_GLOBAL_STANDBY : 0) | \ 88 (ACPI_APM_DEFAULT_SUSPEND_STATE!=0 ? APM_GLOBAL_SUSPEND : 0)) 89 #define ACPI_APM_STATE_MIN (0) 90 #define ACPI_APM_STATE_MAX (4) 91 92 /* It is assumed that there is only acpiapm instance. */ 93 static int resumed = 0, capability_changed = 0; 94 static int standby_state = ACPI_APM_DEFAULT_STANDBY_STATE; 95 static int suspend_state = ACPI_APM_DEFAULT_SUSPEND_STATE; 96 static int capabilities = ACPI_APM_DEFAULT_CAP; 97 static int acpiapm_node = CTL_EOL, standby_node = CTL_EOL; 98 99 struct acpi_softc; 100 extern void acpi_enter_sleep_state(struct acpi_softc *, int); 101 static int acpiapm_match(device_t, cfdata_t , void *); 102 static void acpiapm_attach(device_t, device_t, void *); 103 static int sysctl_state(SYSCTLFN_PROTO); 104 105 CFATTACH_DECL_NEW(acpiapm, sizeof(struct apm_softc), 106 acpiapm_match, acpiapm_attach, NULL, NULL); 107 108 static int 109 /*ARGSUSED*/ 110 acpiapm_match(device_t parent, cfdata_t match, void *aux) 111 { 112 return apm_match(); 113 } 114 115 static void 116 /*ARGSUSED*/ 117 acpiapm_attach(device_t parent, device_t self, void *aux) 118 { 119 struct apm_softc *sc = device_private(self); 120 121 sc->sc_dev = self; 122 sc->sc_ops = &acpiapm_accessops; 123 sc->sc_cookie = parent; 124 sc->sc_vers = 0x0102; 125 sc->sc_detail = 0; 126 sc->sc_hwflags = APM_F_DONT_RUN_HOOKS; 127 apm_attach(sc); 128 } 129 130 static int 131 get_state_value(int id) 132 { 133 const int states[] = { 134 ACPI_STATE_S0, 135 ACPI_STATE_S1, 136 ACPI_STATE_S2, 137 ACPI_STATE_S3, 138 ACPI_STATE_S4 139 }; 140 141 if (id < ACPI_APM_STATE_MIN || id > ACPI_APM_STATE_MAX) 142 return ACPI_STATE_S0; 143 144 return states[id]; 145 } 146 147 static int 148 sysctl_state(SYSCTLFN_ARGS) 149 { 150 int newstate, error, *ref, cap, oldcap; 151 struct sysctlnode node; 152 153 if (rnode->sysctl_num == standby_node) { 154 ref = &standby_state; 155 cap = APM_GLOBAL_STANDBY; 156 } else { 157 ref = &suspend_state; 158 cap = APM_GLOBAL_SUSPEND; 159 } 160 161 newstate = *ref; 162 node = *rnode; 163 node.sysctl_data = &newstate; 164 error = sysctl_lookup(SYSCTLFN_CALL(&node)); 165 if (error || newp == NULL) 166 return error; 167 168 if (newstate < ACPI_APM_STATE_MIN || newstate > ACPI_APM_STATE_MAX) 169 return EINVAL; 170 171 *ref = newstate; 172 oldcap = capabilities; 173 capabilities = newstate != 0 ? oldcap | cap : oldcap & ~cap; 174 if ((capabilities ^ oldcap) != 0) 175 capability_changed = 1; 176 177 return 0; 178 } 179 180 SYSCTL_SETUP(sysctl_acpiapm_setup, "sysctl machdep.acpiapm subtree setup") 181 { 182 const struct sysctlnode *node; 183 184 if (sysctl_createv(clog, 0, NULL, NULL, 185 CTLFLAG_PERMANENT, 186 CTLTYPE_NODE, "machdep", NULL, 187 NULL, 0, NULL, 0, CTL_MACHDEP, CTL_EOL)) 188 return; 189 190 if (sysctl_createv(clog, 0, NULL, &node, 191 CTLFLAG_PERMANENT, 192 CTLTYPE_NODE, "acpiapm", NULL, 193 NULL, 0, NULL, 0, 194 CTL_MACHDEP, CTL_CREATE, CTL_EOL)) 195 return; 196 acpiapm_node = node->sysctl_num; 197 198 if (sysctl_createv(clog, 0, NULL, &node, 199 CTLFLAG_READWRITE, 200 CTLTYPE_INT, "standby", NULL, 201 &sysctl_state, 0, NULL, 0, 202 CTL_MACHDEP, acpiapm_node, CTL_CREATE, CTL_EOL)) 203 return; 204 standby_node = node->sysctl_num; 205 206 if (sysctl_createv(clog, 0, NULL, NULL, 207 CTLFLAG_READWRITE, 208 CTLTYPE_INT, "suspend", NULL, 209 &sysctl_state, 0, NULL, 0, 210 CTL_MACHDEP, acpiapm_node, CTL_CREATE, CTL_EOL)) 211 return; 212 } 213 214 /***************************************************************************** 215 * Minimalistic ACPI /dev/apm emulation support, for ACPI suspend 216 *****************************************************************************/ 217 218 static void 219 /*ARGSUSED*/ 220 acpiapm_disconnect(void *opaque) 221 { 222 return; 223 } 224 225 static void 226 /*ARGSUSED*/ 227 acpiapm_enable(void *opaque, int onoff) 228 { 229 return; 230 } 231 232 static int 233 acpiapm_set_powstate(void *opaque, u_int devid, u_int powstat) 234 { 235 struct acpi_softc *sc = device_private((device_t)opaque); 236 237 if (devid != APM_DEV_ALLDEVS) 238 return APM_ERR_UNRECOG_DEV; 239 240 switch (powstat) { 241 case APM_SYS_READY: 242 break; 243 case APM_SYS_STANDBY: 244 acpi_enter_sleep_state(sc, get_state_value(standby_state)); 245 resumed = 1; 246 break; 247 case APM_SYS_SUSPEND: 248 acpi_enter_sleep_state(sc, get_state_value(suspend_state)); 249 resumed = 1; 250 break; 251 case APM_SYS_OFF: 252 break; 253 case APM_LASTREQ_INPROG: 254 break; 255 case APM_LASTREQ_REJECTED: 256 break; 257 } 258 259 return 0; 260 } 261 262 struct apm_sensor_info { 263 struct apm_power_info *pinfo; 264 int present; 265 int lastcap, descap, cap, warncap, lowcap, discharge; 266 int lastcap_valid, cap_valid, discharge_valid; 267 }; 268 269 static bool 270 apm_per_sensor(const struct sysmon_envsys *sme, const envsys_data_t *edata, 271 void *arg) 272 { 273 struct apm_sensor_info *info = (struct apm_sensor_info *)arg; 274 int data; 275 276 if (sme->sme_class != SME_CLASS_ACADAPTER && 277 sme->sme_class != SME_CLASS_BATTERY) 278 return false; 279 280 if (edata->state == ENVSYS_SINVALID) 281 return true; 282 283 data = edata->value_cur; 284 285 DPRINTF(("%s (%s) %d\n", sme->sme_name, edata->desc, data)); 286 287 if (strstr(edata->desc, "connected")) { 288 info->pinfo->ac_state = data ? APM_AC_ON : APM_AC_OFF; 289 } 290 else if (strstr(edata->desc, "present") && data != 0) 291 info->present++; 292 else if (strstr(edata->desc, "charging")) { 293 if (data) 294 info->pinfo->battery_flags |= APM_BATT_FLAG_CHARGING; 295 else 296 info->pinfo->battery_flags &= ~APM_BATT_FLAG_CHARGING; 297 } 298 else if (strstr(edata->desc, "last full cap")) { 299 info->lastcap += data / 1000; 300 info->lastcap_valid = 1; 301 } 302 else if (strstr(edata->desc, "design cap")) 303 info->descap = data / 1000; 304 else if (strstr(edata->desc, "charge") && 305 strstr(edata->desc, "charge rate") == NULL && 306 strstr(edata->desc, "charge state") == NULL) { 307 308 /* Update cumulative capacity */ 309 info->cap += data / 1000; 310 311 /* get warning- & critical-capacity values */ 312 info->warncap = edata->limits.sel_warnmin / 1000; 313 info->lowcap = edata->limits.sel_critmin / 1000; 314 315 info->cap_valid = 1; 316 info->pinfo->nbattery++; 317 } 318 else if (strstr(edata->desc, "discharge rate")) { 319 info->discharge += data / 1000; 320 info->discharge_valid = 1; 321 } 322 return true; 323 } 324 325 static int 326 /*ARGSUSED*/ 327 acpiapm_get_powstat(void *opaque, u_int batteryid, 328 struct apm_power_info *pinfo) 329 { 330 #define APM_BATT_FLAG_WATERMARK_MASK (APM_BATT_FLAG_CRITICAL | \ 331 APM_BATT_FLAG_LOW | \ 332 APM_BATT_FLAG_HIGH) 333 struct apm_sensor_info info; 334 335 /* Denote most variables as uninitialized. */ 336 info.lowcap = info.warncap = info.descap = -1; 337 338 /* 339 * Prepare to aggregate capacity, charge, and discharge over all 340 * batteries. 341 */ 342 info.cap = info.lastcap = info.discharge = 0; 343 info.cap_valid = info.lastcap_valid = info.discharge_valid = 0; 344 info.present = 0; 345 346 info.pinfo = pinfo; 347 348 (void)memset(pinfo, 0, sizeof(*pinfo)); 349 pinfo->ac_state = APM_AC_UNKNOWN; 350 pinfo->minutes_valid = 0; 351 pinfo->minutes_left = 0; 352 pinfo->batteryid = 0; 353 pinfo->nbattery = 0; /* to be incremented as batteries are found */ 354 pinfo->battery_flags = 0; 355 pinfo->battery_state = APM_BATT_UNKNOWN; /* ignored */ 356 pinfo->battery_life = APM_BATT_LIFE_UNKNOWN; 357 358 sysmon_envsys_foreach_sensor(apm_per_sensor, (void *)&info, true); 359 360 if (info.present == 0) 361 pinfo->battery_flags |= APM_BATT_FLAG_NO_SYSTEM_BATTERY; 362 363 if (info.cap_valid > 0) { 364 if (info.warncap != -1 && info.cap < info.warncap) 365 pinfo->battery_flags |= APM_BATT_FLAG_CRITICAL; 366 else if (info.lowcap != -1) { 367 if (info.cap < info.lowcap) 368 pinfo->battery_flags |= APM_BATT_FLAG_LOW; 369 else 370 pinfo->battery_flags |= APM_BATT_FLAG_HIGH; 371 } 372 if (info.lastcap_valid > 0 && info.lastcap != 0) 373 pinfo->battery_life = 100 * info.cap / info.lastcap; 374 else if (info.descap != -1 && info.descap != 0) 375 pinfo->battery_life = 100 * info.cap / info.descap; 376 } 377 378 if ((pinfo->battery_flags & APM_BATT_FLAG_CHARGING) == 0) { 379 /* discharging */ 380 if (info.discharge != -1 && info.discharge != 0 && 381 info.cap != -1) 382 pinfo->minutes_left = 60 * info.cap / info.discharge; 383 } 384 if ((pinfo->battery_flags & APM_BATT_FLAG_WATERMARK_MASK) == 0 && 385 (pinfo->battery_flags & APM_BATT_FLAG_NO_SYSTEM_BATTERY) == 0) { 386 if (pinfo->ac_state == APM_AC_ON) 387 pinfo->battery_flags |= APM_BATT_FLAG_HIGH; 388 else 389 pinfo->battery_flags |= APM_BATT_FLAG_LOW; 390 } 391 392 DPRINTF(("%d %d %d %d %d %d\n", info.cap, info.warncap, info.lowcap, 393 info.lastcap, info.descap, info.discharge)); 394 DPRINTF(("pinfo %d %d %d\n", pinfo->battery_flags, 395 pinfo->battery_life, pinfo->battery_life)); 396 return 0; 397 } 398 399 static int 400 /*ARGSUSED*/ 401 acpiapm_get_event(void *opaque, u_int *event_type, u_int *event_info) 402 { 403 if (capability_changed) { 404 capability_changed = 0; 405 *event_type = APM_CAP_CHANGE; 406 *event_info = 0; 407 return 0; 408 } 409 if (resumed) { 410 resumed = 0; 411 *event_type = APM_NORMAL_RESUME; 412 *event_info = 0; 413 return 0; 414 } 415 416 return APM_ERR_NOEVENTS; 417 } 418 419 static void 420 /*ARGSUSED*/ 421 acpiapm_cpu_busy(void *opaque) 422 { 423 return; 424 } 425 426 static void 427 /*ARGSUSED*/ 428 acpiapm_cpu_idle(void *opaque) 429 { 430 return; 431 } 432 433 static void 434 /*ARGSUSED*/ 435 acpiapm_get_capabilities(void *opaque, u_int *numbatts, 436 u_int *capflags) 437 { 438 *numbatts = 1; 439 *capflags = capabilities; 440 return; 441 } 442