1 /* $NetBSD: sysmon_power.c,v 1.10 2004/05/22 11:32:50 cube 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.10 2004/05/22 11:32:50 cube 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, int flag, int mode, struct proc *p) 150 { 151 int error = 0; 152 153 simple_lock(&sysmon_power_event_queue_slock); 154 if (sysmon_power_daemon != NULL) 155 error = EBUSY; 156 else { 157 sysmon_power_daemon = p; 158 sysmon_power_event_queue_flush(); 159 } 160 simple_unlock(&sysmon_power_event_queue_slock); 161 162 return (error); 163 } 164 165 /* 166 * sysmonclose_power: 167 * 168 * Close the system monitor device. 169 */ 170 int 171 sysmonclose_power(dev_t dev, int flag, int mode, struct proc *p) 172 { 173 int count; 174 175 simple_lock(&sysmon_power_event_queue_slock); 176 count = sysmon_power_event_queue_count; 177 sysmon_power_daemon = NULL; 178 sysmon_power_event_queue_flush(); 179 simple_unlock(&sysmon_power_event_queue_slock); 180 181 if (count) 182 printf("WARNING: %d power events lost by exiting daemon\n", 183 count); 184 185 return (0); 186 } 187 188 /* 189 * sysmonread_power: 190 * 191 * Read the system monitor device. 192 */ 193 int 194 sysmonread_power(dev_t dev, struct uio *uio, int flags) 195 { 196 power_event_t pev; 197 int error; 198 199 /* We only allow one event to be read at a time. */ 200 if (uio->uio_resid != POWER_EVENT_MSG_SIZE) 201 return (EINVAL); 202 203 simple_lock(&sysmon_power_event_queue_slock); 204 again: 205 if (sysmon_get_power_event(&pev)) { 206 simple_unlock(&sysmon_power_event_queue_slock); 207 return (uiomove(&pev, POWER_EVENT_MSG_SIZE, uio)); 208 } 209 210 if (flags & IO_NDELAY) { 211 simple_unlock(&sysmon_power_event_queue_slock); 212 return (EWOULDBLOCK); 213 } 214 215 sysmon_power_event_queue_flags |= PEVQ_F_WAITING; 216 error = ltsleep(&sysmon_power_event_queue_count, 217 PRIBIO|PCATCH, "smpower", 0, &sysmon_power_event_queue_slock); 218 if (error) { 219 simple_unlock(&sysmon_power_event_queue_slock); 220 return (error); 221 } 222 goto again; 223 } 224 225 /* 226 * sysmonpoll_power: 227 * 228 * Poll the system monitor device. 229 */ 230 int 231 sysmonpoll_power(dev_t dev, int events, struct proc *p) 232 { 233 int revents; 234 235 revents = events & (POLLOUT | POLLWRNORM); 236 237 /* Attempt to save some work. */ 238 if ((events & (POLLIN | POLLRDNORM)) == 0) 239 return (revents); 240 241 simple_lock(&sysmon_power_event_queue_slock); 242 if (sysmon_power_event_queue_count) 243 revents |= events & (POLLIN | POLLRDNORM); 244 else 245 selrecord(p, &sysmon_power_event_queue_selinfo); 246 simple_unlock(&sysmon_power_event_queue_slock); 247 248 return (revents); 249 } 250 251 static void 252 filt_sysmon_power_rdetach(struct knote *kn) 253 { 254 255 simple_lock(&sysmon_power_event_queue_slock); 256 SLIST_REMOVE(&sysmon_power_event_queue_selinfo.sel_klist, 257 kn, knote, kn_selnext); 258 simple_unlock(&sysmon_power_event_queue_slock); 259 } 260 261 static int 262 filt_sysmon_power_read(struct knote *kn, long hint) 263 { 264 265 simple_lock(&sysmon_power_event_queue_slock); 266 kn->kn_data = sysmon_power_event_queue_count; 267 simple_unlock(&sysmon_power_event_queue_slock); 268 269 return (kn->kn_data > 0); 270 } 271 272 static const struct filterops sysmon_power_read_filtops = 273 { 1, NULL, filt_sysmon_power_rdetach, filt_sysmon_power_read }; 274 275 static const struct filterops sysmon_power_write_filtops = 276 { 1, NULL, filt_sysmon_power_rdetach, filt_seltrue }; 277 278 /* 279 * sysmonkqfilter_power: 280 * 281 * Kqueue filter for the system monitor device. 282 */ 283 int 284 sysmonkqfilter_power(dev_t dev, struct knote *kn) 285 { 286 struct klist *klist; 287 288 switch (kn->kn_filter) { 289 case EVFILT_READ: 290 klist = &sysmon_power_event_queue_selinfo.sel_klist; 291 kn->kn_fop = &sysmon_power_read_filtops; 292 break; 293 294 case EVFILT_WRITE: 295 klist = &sysmon_power_event_queue_selinfo.sel_klist; 296 kn->kn_fop = &sysmon_power_write_filtops; 297 break; 298 299 default: 300 return (1); 301 } 302 303 simple_lock(&sysmon_power_event_queue_slock); 304 SLIST_INSERT_HEAD(klist, kn, kn_selnext); 305 simple_unlock(&sysmon_power_event_queue_slock); 306 307 return (0); 308 } 309 310 /* 311 * sysmonioctl_power: 312 * 313 * Perform a power managmenet control request. 314 */ 315 int 316 sysmonioctl_power(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p) 317 { 318 int error = 0; 319 320 switch (cmd) { 321 case POWER_IOC_GET_TYPE: 322 { 323 struct power_type *power_type = (void *) data; 324 325 strcpy(power_type->power_type, sysmon_power_type); 326 break; 327 } 328 default: 329 error = ENOTTY; 330 } 331 332 return (error); 333 } 334 335 /* 336 * sysmon_power_settype: 337 * 338 * Sets the back-end power management type. This information can 339 * be used by the power management daemon. 340 */ 341 void 342 sysmon_power_settype(const char *type) 343 { 344 345 /* 346 * Don't bother locking this; it's going to be set 347 * during autoconfiguration, and then only read from 348 * then on. 349 */ 350 strcpy(sysmon_power_type, type); 351 } 352 353 /* 354 * sysmon_pswitch_register: 355 * 356 * Register a power switch device. 357 */ 358 int 359 sysmon_pswitch_register(struct sysmon_pswitch *smpsw) 360 { 361 362 simple_lock(&sysmon_pswitch_list_slock); 363 LIST_INSERT_HEAD(&sysmon_pswitch_list, smpsw, smpsw_list); 364 simple_unlock(&sysmon_pswitch_list_slock); 365 366 return (0); 367 } 368 369 /* 370 * sysmon_pswitch_unregister: 371 * 372 * Unregister a power switch device. 373 */ 374 void 375 sysmon_pswitch_unregister(struct sysmon_pswitch *smpsw) 376 { 377 378 simple_lock(&sysmon_pswitch_list_slock); 379 LIST_REMOVE(smpsw, smpsw_list); 380 simple_unlock(&sysmon_pswitch_list_slock); 381 } 382 383 /* 384 * sysmon_pswitch_event: 385 * 386 * Register an event on a power switch device. 387 */ 388 void 389 sysmon_pswitch_event(struct sysmon_pswitch *smpsw, int event) 390 { 391 392 /* 393 * If a power management daemon is connected, then simply 394 * deliver the event to them. If not, we need to try to 395 * do something reasonable ourselves. 396 */ 397 simple_lock(&sysmon_power_event_queue_slock); 398 if (sysmon_power_daemon != NULL) { 399 power_event_t pev; 400 int rv; 401 402 pev.pev_type = POWER_EVENT_SWITCH_STATE_CHANGE; 403 pev.pev_switch.psws_state = event; 404 pev.pev_switch.psws_type = smpsw->smpsw_type; 405 strcpy(pev.pev_switch.psws_name, smpsw->smpsw_name); 406 407 rv = sysmon_queue_power_event(&pev); 408 if (rv == 0) { 409 simple_unlock(&sysmon_power_event_queue_slock); 410 printf("%s: WARNING: state change event %d lost; " 411 "queue full\n", smpsw->smpsw_name, 412 pev.pev_type); 413 return; 414 } else { 415 if (sysmon_power_event_queue_flags & PEVQ_F_WAITING) { 416 sysmon_power_event_queue_flags &= ~PEVQ_F_WAITING; 417 simple_unlock(&sysmon_power_event_queue_slock); 418 wakeup(&sysmon_power_event_queue_count); 419 } else { 420 simple_unlock(&sysmon_power_event_queue_slock); 421 } 422 selnotify(&sysmon_power_event_queue_selinfo, 0); 423 return; 424 } 425 } 426 simple_unlock(&sysmon_power_event_queue_slock); 427 428 switch (smpsw->smpsw_type) { 429 case PSWITCH_TYPE_POWER: 430 if (event != PSWITCH_EVENT_PRESSED) { 431 /* just ignore it */ 432 return; 433 } 434 435 /* 436 * Attempt a somewhat graceful shutdown of the system, 437 * as if the user has issued a reboot(2) call with 438 * RB_POWERDOWN. 439 */ 440 printf("%s: power button pressed, shutting down!\n", 441 smpsw->smpsw_name); 442 cpu_reboot(RB_POWERDOWN, NULL); 443 break; 444 445 case PSWITCH_TYPE_RESET: 446 if (event != PSWITCH_EVENT_PRESSED) { 447 /* just ignore it */ 448 return; 449 } 450 451 /* 452 * Attempt a somewhat graceful reboot of the system, 453 * as if the user had issued a reboot(2) call. 454 */ 455 printf("%s: reset button pressed, rebooting!\n", 456 smpsw->smpsw_name); 457 cpu_reboot(0, NULL); 458 break; 459 460 case PSWITCH_TYPE_SLEEP: 461 if (event != PSWITCH_EVENT_PRESSED) { 462 /* just ignore it */ 463 return; 464 } 465 466 /* 467 * Try to enter a "sleep" state. 468 */ 469 /* XXX */ 470 printf("%s: sleep button pressed.\n", smpsw->smpsw_name); 471 break; 472 473 case PSWITCH_TYPE_LID: 474 switch (event) { 475 case PSWITCH_EVENT_PRESSED: 476 /* 477 * Try to enter a "standby" state. 478 */ 479 /* XXX */ 480 printf("%s: lid closed.\n", smpsw->smpsw_name); 481 break; 482 483 case PSWITCH_EVENT_RELEASED: 484 /* 485 * Come out of "standby" state. 486 */ 487 /* XXX */ 488 printf("%s: lid opened.\n", smpsw->smpsw_name); 489 break; 490 491 default: 492 printf("%s: unknown lid switch event: %d\n", 493 smpsw->smpsw_name, event); 494 } 495 break; 496 497 case PSWITCH_TYPE_ACADAPTER: 498 switch (event) { 499 case PSWITCH_EVENT_PRESSED: 500 /* 501 * Come out of power-save state. 502 */ 503 printf("%s: AC adapter online.\n", smpsw->smpsw_name); 504 break; 505 506 case PSWITCH_EVENT_RELEASED: 507 /* 508 * Try to enter a power-save state. 509 */ 510 printf("%s: AC adapter offline.\n", smpsw->smpsw_name); 511 break; 512 } 513 break; 514 515 default: 516 printf("%s: sysmon_pswitch_event can't handle me.\n", 517 smpsw->smpsw_name); 518 } 519 } 520