1 /* $NetBSD: sysmon_power.c,v 1.13 2006/10/12 01:31:59 christos Exp $ */ 2 3 /* 4 * Copyright (c) 2003 Wasabi Systems, Inc. 5 * All rights reserved. 6 * 7 * Written by Jason R. Thorpe for Wasabi Systems, Inc. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 3. All advertising materials mentioning features or use of this software 18 * must display the following acknowledgement: 19 * This product includes software developed for the NetBSD Project by 20 * Wasabi Systems, Inc. 21 * 4. The name of Wasabi Systems, Inc. may not be used to endorse 22 * or promote products derived from this software without specific prior 23 * written permission. 24 * 25 * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND 26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 27 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 28 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL WASABI SYSTEMS, INC 29 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 30 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 31 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 32 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 33 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 34 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 35 * POSSIBILITY OF SUCH DAMAGE. 36 */ 37 38 /* 39 * Power management framework for sysmon. 40 * 41 * We defer to a power management daemon running in userspace, since 42 * power management is largely a policy issue. This merely provides 43 * for power management event notification to that daemon. 44 */ 45 46 #include <sys/cdefs.h> 47 __KERNEL_RCSID(0, "$NetBSD: sysmon_power.c,v 1.13 2006/10/12 01:31:59 christos Exp $"); 48 49 #include <sys/param.h> 50 #include <sys/reboot.h> 51 #include <sys/systm.h> 52 #include <sys/poll.h> 53 #include <sys/select.h> 54 #include <sys/vnode.h> 55 56 #include <dev/sysmon/sysmonvar.h> 57 58 static LIST_HEAD(, sysmon_pswitch) sysmon_pswitch_list = 59 LIST_HEAD_INITIALIZER(sysmon_pswitch_list); 60 static struct simplelock sysmon_pswitch_list_slock = 61 SIMPLELOCK_INITIALIZER; 62 63 static struct proc *sysmon_power_daemon; 64 65 #define SYSMON_MAX_POWER_EVENTS 32 66 67 static struct simplelock sysmon_power_event_queue_slock = 68 SIMPLELOCK_INITIALIZER; 69 static power_event_t sysmon_power_event_queue[SYSMON_MAX_POWER_EVENTS]; 70 static int sysmon_power_event_queue_head; 71 static int sysmon_power_event_queue_tail; 72 static int sysmon_power_event_queue_count; 73 static int sysmon_power_event_queue_flags; 74 static struct selinfo sysmon_power_event_queue_selinfo; 75 76 static char sysmon_power_type[32]; 77 78 #define PEVQ_F_WAITING 0x01 /* daemon waiting for event */ 79 80 #define SYSMON_NEXT_EVENT(x) (((x) + 1) % SYSMON_MAX_POWER_EVENTS) 81 82 /* 83 * sysmon_queue_power_event: 84 * 85 * Enqueue a power event for the power mangement daemon. Returns 86 * non-zero if we were able to enqueue a power event. 87 */ 88 static int 89 sysmon_queue_power_event(power_event_t *pev) 90 { 91 92 LOCK_ASSERT(simple_lock_held(&sysmon_power_event_queue_slock)); 93 94 if (sysmon_power_event_queue_count == SYSMON_MAX_POWER_EVENTS) 95 return (0); 96 97 sysmon_power_event_queue[sysmon_power_event_queue_head] = *pev; 98 sysmon_power_event_queue_head = 99 SYSMON_NEXT_EVENT(sysmon_power_event_queue_head); 100 sysmon_power_event_queue_count++; 101 102 return (1); 103 } 104 105 /* 106 * sysmon_get_power_event: 107 * 108 * Get a power event from the queue. Returns non-zero if there 109 * is an event available. 110 */ 111 static int 112 sysmon_get_power_event(power_event_t *pev) 113 { 114 115 LOCK_ASSERT(simple_lock_held(&sysmon_power_event_queue_slock)); 116 117 if (sysmon_power_event_queue_count == 0) 118 return (0); 119 120 *pev = sysmon_power_event_queue[sysmon_power_event_queue_tail]; 121 sysmon_power_event_queue_tail = 122 SYSMON_NEXT_EVENT(sysmon_power_event_queue_tail); 123 sysmon_power_event_queue_count--; 124 125 return (1); 126 } 127 128 /* 129 * sysmon_power_event_queue_flush: 130 * 131 * Flush the event queue, and reset all state. 132 */ 133 static void 134 sysmon_power_event_queue_flush(void) 135 { 136 137 sysmon_power_event_queue_head = 0; 138 sysmon_power_event_queue_tail = 0; 139 sysmon_power_event_queue_count = 0; 140 sysmon_power_event_queue_flags = 0; 141 } 142 143 /* 144 * sysmonopen_power: 145 * 146 * Open the system monitor device. 147 */ 148 int 149 sysmonopen_power(dev_t dev __unused, int flag __unused, int mode __unused, 150 struct lwp *l) 151 { 152 int error = 0; 153 154 simple_lock(&sysmon_power_event_queue_slock); 155 if (sysmon_power_daemon != NULL) 156 error = EBUSY; 157 else { 158 sysmon_power_daemon = l->l_proc; 159 sysmon_power_event_queue_flush(); 160 } 161 simple_unlock(&sysmon_power_event_queue_slock); 162 163 return (error); 164 } 165 166 /* 167 * sysmonclose_power: 168 * 169 * Close the system monitor device. 170 */ 171 int 172 sysmonclose_power(dev_t dev __unused, int flag __unused, int mode __unused, 173 struct lwp *l __unused) 174 { 175 int count; 176 177 simple_lock(&sysmon_power_event_queue_slock); 178 count = sysmon_power_event_queue_count; 179 sysmon_power_daemon = NULL; 180 sysmon_power_event_queue_flush(); 181 simple_unlock(&sysmon_power_event_queue_slock); 182 183 if (count) 184 printf("WARNING: %d power events lost by exiting daemon\n", 185 count); 186 187 return (0); 188 } 189 190 /* 191 * sysmonread_power: 192 * 193 * Read the system monitor device. 194 */ 195 int 196 sysmonread_power(dev_t dev __unused, struct uio *uio, int flags) 197 { 198 power_event_t pev; 199 int error; 200 201 /* We only allow one event to be read at a time. */ 202 if (uio->uio_resid != POWER_EVENT_MSG_SIZE) 203 return (EINVAL); 204 205 simple_lock(&sysmon_power_event_queue_slock); 206 again: 207 if (sysmon_get_power_event(&pev)) { 208 simple_unlock(&sysmon_power_event_queue_slock); 209 return (uiomove(&pev, POWER_EVENT_MSG_SIZE, uio)); 210 } 211 212 if (flags & IO_NDELAY) { 213 simple_unlock(&sysmon_power_event_queue_slock); 214 return (EWOULDBLOCK); 215 } 216 217 sysmon_power_event_queue_flags |= PEVQ_F_WAITING; 218 error = ltsleep(&sysmon_power_event_queue_count, 219 PRIBIO|PCATCH, "smpower", 0, &sysmon_power_event_queue_slock); 220 if (error) { 221 simple_unlock(&sysmon_power_event_queue_slock); 222 return (error); 223 } 224 goto again; 225 } 226 227 /* 228 * sysmonpoll_power: 229 * 230 * Poll the system monitor device. 231 */ 232 int 233 sysmonpoll_power(dev_t dev __unused, int events, struct lwp *l) 234 { 235 int revents; 236 237 revents = events & (POLLOUT | POLLWRNORM); 238 239 /* Attempt to save some work. */ 240 if ((events & (POLLIN | POLLRDNORM)) == 0) 241 return (revents); 242 243 simple_lock(&sysmon_power_event_queue_slock); 244 if (sysmon_power_event_queue_count) 245 revents |= events & (POLLIN | POLLRDNORM); 246 else 247 selrecord(l, &sysmon_power_event_queue_selinfo); 248 simple_unlock(&sysmon_power_event_queue_slock); 249 250 return (revents); 251 } 252 253 static void 254 filt_sysmon_power_rdetach(struct knote *kn) 255 { 256 257 simple_lock(&sysmon_power_event_queue_slock); 258 SLIST_REMOVE(&sysmon_power_event_queue_selinfo.sel_klist, 259 kn, knote, kn_selnext); 260 simple_unlock(&sysmon_power_event_queue_slock); 261 } 262 263 static int 264 filt_sysmon_power_read(struct knote *kn, long hint __unused) 265 { 266 267 simple_lock(&sysmon_power_event_queue_slock); 268 kn->kn_data = sysmon_power_event_queue_count; 269 simple_unlock(&sysmon_power_event_queue_slock); 270 271 return (kn->kn_data > 0); 272 } 273 274 static const struct filterops sysmon_power_read_filtops = 275 { 1, NULL, filt_sysmon_power_rdetach, filt_sysmon_power_read }; 276 277 static const struct filterops sysmon_power_write_filtops = 278 { 1, NULL, filt_sysmon_power_rdetach, filt_seltrue }; 279 280 /* 281 * sysmonkqfilter_power: 282 * 283 * Kqueue filter for the system monitor device. 284 */ 285 int 286 sysmonkqfilter_power(dev_t dev __unused, struct knote *kn) 287 { 288 struct klist *klist; 289 290 switch (kn->kn_filter) { 291 case EVFILT_READ: 292 klist = &sysmon_power_event_queue_selinfo.sel_klist; 293 kn->kn_fop = &sysmon_power_read_filtops; 294 break; 295 296 case EVFILT_WRITE: 297 klist = &sysmon_power_event_queue_selinfo.sel_klist; 298 kn->kn_fop = &sysmon_power_write_filtops; 299 break; 300 301 default: 302 return (1); 303 } 304 305 simple_lock(&sysmon_power_event_queue_slock); 306 SLIST_INSERT_HEAD(klist, kn, kn_selnext); 307 simple_unlock(&sysmon_power_event_queue_slock); 308 309 return (0); 310 } 311 312 /* 313 * sysmonioctl_power: 314 * 315 * Perform a power managmenet control request. 316 */ 317 int 318 sysmonioctl_power(dev_t dev __unused, u_long cmd, caddr_t data, 319 int flag __unused, struct lwp *l __unused) 320 { 321 int error = 0; 322 323 switch (cmd) { 324 case POWER_IOC_GET_TYPE: 325 { 326 struct power_type *power_type = (void *) data; 327 328 strcpy(power_type->power_type, sysmon_power_type); 329 break; 330 } 331 default: 332 error = ENOTTY; 333 } 334 335 return (error); 336 } 337 338 /* 339 * sysmon_power_settype: 340 * 341 * Sets the back-end power management type. This information can 342 * be used by the power management daemon. 343 */ 344 void 345 sysmon_power_settype(const char *type) 346 { 347 348 /* 349 * Don't bother locking this; it's going to be set 350 * during autoconfiguration, and then only read from 351 * then on. 352 */ 353 strcpy(sysmon_power_type, type); 354 } 355 356 /* 357 * sysmon_pswitch_register: 358 * 359 * Register a power switch device. 360 */ 361 int 362 sysmon_pswitch_register(struct sysmon_pswitch *smpsw) 363 { 364 365 simple_lock(&sysmon_pswitch_list_slock); 366 LIST_INSERT_HEAD(&sysmon_pswitch_list, smpsw, smpsw_list); 367 simple_unlock(&sysmon_pswitch_list_slock); 368 369 return (0); 370 } 371 372 /* 373 * sysmon_pswitch_unregister: 374 * 375 * Unregister a power switch device. 376 */ 377 void 378 sysmon_pswitch_unregister(struct sysmon_pswitch *smpsw) 379 { 380 381 simple_lock(&sysmon_pswitch_list_slock); 382 LIST_REMOVE(smpsw, smpsw_list); 383 simple_unlock(&sysmon_pswitch_list_slock); 384 } 385 386 /* 387 * sysmon_pswitch_event: 388 * 389 * Register an event on a power switch device. 390 */ 391 void 392 sysmon_pswitch_event(struct sysmon_pswitch *smpsw, int event) 393 { 394 395 /* 396 * If a power management daemon is connected, then simply 397 * deliver the event to them. If not, we need to try to 398 * do something reasonable ourselves. 399 */ 400 simple_lock(&sysmon_power_event_queue_slock); 401 if (sysmon_power_daemon != NULL) { 402 power_event_t pev; 403 int rv; 404 405 pev.pev_type = POWER_EVENT_SWITCH_STATE_CHANGE; 406 pev.pev_switch.psws_state = event; 407 pev.pev_switch.psws_type = smpsw->smpsw_type; 408 strcpy(pev.pev_switch.psws_name, smpsw->smpsw_name); 409 410 rv = sysmon_queue_power_event(&pev); 411 if (rv == 0) { 412 simple_unlock(&sysmon_power_event_queue_slock); 413 printf("%s: WARNING: state change event %d lost; " 414 "queue full\n", smpsw->smpsw_name, 415 pev.pev_type); 416 return; 417 } else { 418 if (sysmon_power_event_queue_flags & PEVQ_F_WAITING) { 419 sysmon_power_event_queue_flags &= ~PEVQ_F_WAITING; 420 simple_unlock(&sysmon_power_event_queue_slock); 421 wakeup(&sysmon_power_event_queue_count); 422 } else { 423 simple_unlock(&sysmon_power_event_queue_slock); 424 } 425 selnotify(&sysmon_power_event_queue_selinfo, 0); 426 return; 427 } 428 } 429 simple_unlock(&sysmon_power_event_queue_slock); 430 431 switch (smpsw->smpsw_type) { 432 case PSWITCH_TYPE_POWER: 433 if (event != PSWITCH_EVENT_PRESSED) { 434 /* just ignore it */ 435 return; 436 } 437 438 /* 439 * Attempt a somewhat graceful shutdown of the system, 440 * as if the user has issued a reboot(2) call with 441 * RB_POWERDOWN. 442 */ 443 printf("%s: power button pressed, shutting down!\n", 444 smpsw->smpsw_name); 445 cpu_reboot(RB_POWERDOWN, NULL); 446 break; 447 448 case PSWITCH_TYPE_RESET: 449 if (event != PSWITCH_EVENT_PRESSED) { 450 /* just ignore it */ 451 return; 452 } 453 454 /* 455 * Attempt a somewhat graceful reboot of the system, 456 * as if the user had issued a reboot(2) call. 457 */ 458 printf("%s: reset button pressed, rebooting!\n", 459 smpsw->smpsw_name); 460 cpu_reboot(0, NULL); 461 break; 462 463 case PSWITCH_TYPE_SLEEP: 464 if (event != PSWITCH_EVENT_PRESSED) { 465 /* just ignore it */ 466 return; 467 } 468 469 /* 470 * Try to enter a "sleep" state. 471 */ 472 /* XXX */ 473 printf("%s: sleep button pressed.\n", smpsw->smpsw_name); 474 break; 475 476 case PSWITCH_TYPE_LID: 477 switch (event) { 478 case PSWITCH_EVENT_PRESSED: 479 /* 480 * Try to enter a "standby" state. 481 */ 482 /* XXX */ 483 printf("%s: lid closed.\n", smpsw->smpsw_name); 484 break; 485 486 case PSWITCH_EVENT_RELEASED: 487 /* 488 * Come out of "standby" state. 489 */ 490 /* XXX */ 491 printf("%s: lid opened.\n", smpsw->smpsw_name); 492 break; 493 494 default: 495 printf("%s: unknown lid switch event: %d\n", 496 smpsw->smpsw_name, event); 497 } 498 break; 499 500 case PSWITCH_TYPE_ACADAPTER: 501 switch (event) { 502 case PSWITCH_EVENT_PRESSED: 503 /* 504 * Come out of power-save state. 505 */ 506 printf("%s: AC adapter online.\n", smpsw->smpsw_name); 507 break; 508 509 case PSWITCH_EVENT_RELEASED: 510 /* 511 * Try to enter a power-save state. 512 */ 513 printf("%s: AC adapter offline.\n", smpsw->smpsw_name); 514 break; 515 } 516 break; 517 518 default: 519 printf("%s: sysmon_pswitch_event can't handle me.\n", 520 smpsw->smpsw_name); 521 } 522 } 523