1 /* $NetBSD: sysmon_power.c,v 1.5 2003/05/19 23:24:55 kochi 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/param.h> 47 #include <sys/reboot.h> 48 #include <sys/systm.h> 49 #include <sys/poll.h> 50 #include <sys/select.h> 51 #include <sys/vnode.h> 52 53 #include <dev/sysmon/sysmonvar.h> 54 55 static LIST_HEAD(, sysmon_pswitch) sysmon_pswitch_list = 56 LIST_HEAD_INITIALIZER(sysmon_pswitch_list); 57 static struct simplelock sysmon_pswitch_list_slock = 58 SIMPLELOCK_INITIALIZER; 59 60 static struct proc *sysmon_power_daemon; 61 62 #define SYSMON_MAX_POWER_EVENTS 32 63 64 static struct simplelock sysmon_power_event_queue_slock = 65 SIMPLELOCK_INITIALIZER; 66 static power_event_t sysmon_power_event_queue[SYSMON_MAX_POWER_EVENTS]; 67 static int sysmon_power_event_queue_head; 68 static int sysmon_power_event_queue_tail; 69 static int sysmon_power_event_queue_count; 70 static int sysmon_power_event_queue_flags; 71 static struct selinfo sysmon_power_event_queue_selinfo; 72 73 static char sysmon_power_type[32]; 74 75 #define PEVQ_F_WAITING 0x01 /* daemon waiting for event */ 76 77 #define SYSMON_NEXT_EVENT(x) (((x) + 1) / SYSMON_MAX_POWER_EVENTS) 78 79 /* 80 * sysmon_queue_power_event: 81 * 82 * Enqueue a power event for the power mangement daemon. Returns 83 * non-zero if we were able to enqueue a power event. 84 */ 85 static int 86 sysmon_queue_power_event(power_event_t *pev) 87 { 88 89 LOCK_ASSERT(simple_lock_held(&sysmon_power_event_queue_slock)); 90 91 if (sysmon_power_event_queue_count == SYSMON_MAX_POWER_EVENTS) 92 return (0); 93 94 sysmon_power_event_queue[sysmon_power_event_queue_head] = *pev; 95 sysmon_power_event_queue_head = 96 SYSMON_NEXT_EVENT(sysmon_power_event_queue_head); 97 sysmon_power_event_queue_count++; 98 99 return (1); 100 } 101 102 /* 103 * sysmon_get_power_event: 104 * 105 * Get a power event from the queue. Returns non-zero if there 106 * is an event available. 107 */ 108 static int 109 sysmon_get_power_event(power_event_t *pev) 110 { 111 112 LOCK_ASSERT(simple_lock_held(&sysmon_power_event_queue_slock)); 113 114 if (sysmon_power_event_queue_count == 0) 115 return (0); 116 117 *pev = sysmon_power_event_queue[sysmon_power_event_queue_tail]; 118 sysmon_power_event_queue_tail = 119 SYSMON_NEXT_EVENT(sysmon_power_event_queue_tail); 120 sysmon_power_event_queue_count--; 121 122 return (1); 123 } 124 125 /* 126 * sysmon_power_event_queue_flush: 127 * 128 * Flush the event queue, and reset all state. 129 */ 130 static void 131 sysmon_power_event_queue_flush(void) 132 { 133 134 sysmon_power_event_queue_head = 0; 135 sysmon_power_event_queue_tail = 0; 136 sysmon_power_event_queue_count = 0; 137 sysmon_power_event_queue_flags = 0; 138 } 139 140 /* 141 * sysmonopen_power: 142 * 143 * Open the system monitor device. 144 */ 145 int 146 sysmonopen_power(dev_t dev, int flag, int mode, struct proc *p) 147 { 148 int error = 0; 149 150 simple_lock(&sysmon_power_event_queue_slock); 151 if (sysmon_power_daemon != NULL) 152 error = EBUSY; 153 else { 154 sysmon_power_daemon = p; 155 sysmon_power_event_queue_flush(); 156 } 157 simple_unlock(&sysmon_power_event_queue_slock); 158 159 return (error); 160 } 161 162 /* 163 * sysmonclose_power: 164 * 165 * Close the system monitor device. 166 */ 167 int 168 sysmonclose_power(dev_t dev, int flag, int mode, struct proc *p) 169 { 170 int count; 171 172 simple_lock(&sysmon_power_event_queue_slock); 173 count = sysmon_power_event_queue_count; 174 sysmon_power_daemon = NULL; 175 sysmon_power_event_queue_flush(); 176 simple_unlock(&sysmon_power_event_queue_slock); 177 178 if (count) 179 printf("WARNING: %d power events lost by exiting daemon\n", 180 count); 181 182 return (0); 183 } 184 185 /* 186 * sysmonread_power: 187 * 188 * Read the system monitor device. 189 */ 190 int 191 sysmonread_power(dev_t dev, struct uio *uio, int flags) 192 { 193 power_event_t pev; 194 int error; 195 196 /* We only allow one event to be read at a time. */ 197 if (uio->uio_resid != POWER_EVENT_MSG_SIZE) 198 return (EINVAL); 199 200 simple_lock(&sysmon_power_event_queue_slock); 201 again: 202 if (sysmon_get_power_event(&pev)) { 203 simple_unlock(&sysmon_power_event_queue_slock); 204 return (uiomove(&pev, POWER_EVENT_MSG_SIZE, uio)); 205 } 206 207 if (flags & IO_NDELAY) { 208 simple_unlock(&sysmon_power_event_queue_slock); 209 return (EWOULDBLOCK); 210 } 211 212 sysmon_power_event_queue_flags |= PEVQ_F_WAITING; 213 error = ltsleep(&sysmon_power_event_queue_count, 214 PRIBIO|PCATCH, "smpower", 0, &sysmon_power_event_queue_slock); 215 if (error) { 216 simple_unlock(&sysmon_power_event_queue_slock); 217 return (error); 218 } 219 goto again; 220 } 221 222 /* 223 * sysmonpoll_power: 224 * 225 * Poll the system monitor device. 226 */ 227 int 228 sysmonpoll_power(dev_t dev, int events, struct proc *p) 229 { 230 int revents; 231 232 revents = events & (POLLOUT | POLLWRNORM); 233 234 /* Attempt to save some work. */ 235 if ((events & (POLLIN | POLLRDNORM)) == 0) 236 return (revents); 237 238 simple_lock(&sysmon_power_event_queue_slock); 239 if (sysmon_power_event_queue_count) 240 revents |= events & (POLLIN | POLLRDNORM); 241 else 242 selrecord(p, &sysmon_power_event_queue_selinfo); 243 simple_unlock(&sysmon_power_event_queue_slock); 244 245 return (revents); 246 } 247 248 static void 249 filt_sysmon_power_rdetach(struct knote *kn) 250 { 251 252 simple_lock(&sysmon_power_event_queue_slock); 253 SLIST_REMOVE(&sysmon_power_event_queue_selinfo.sel_klist, 254 kn, knote, kn_selnext); 255 simple_unlock(&sysmon_power_event_queue_slock); 256 } 257 258 static int 259 filt_sysmon_power_read(struct knote *kn, long hint) 260 { 261 262 simple_lock(&sysmon_power_event_queue_slock); 263 kn->kn_data = sysmon_power_event_queue_count; 264 simple_unlock(&sysmon_power_event_queue_slock); 265 266 return (kn->kn_data > 0); 267 } 268 269 static const struct filterops sysmon_power_read_filtops = 270 { 1, NULL, filt_sysmon_power_rdetach, filt_sysmon_power_read }; 271 272 static const struct filterops sysmon_power_write_filtops = 273 { 1, NULL, filt_sysmon_power_rdetach, filt_seltrue }; 274 275 /* 276 * sysmonkqfilter_power: 277 * 278 * Kqueue filter for the system monitor device. 279 */ 280 int 281 sysmonkqfilter_power(dev_t dev, struct knote *kn) 282 { 283 struct klist *klist; 284 285 switch (kn->kn_filter) { 286 case EVFILT_READ: 287 klist = &sysmon_power_event_queue_selinfo.sel_klist; 288 kn->kn_fop = &sysmon_power_read_filtops; 289 break; 290 291 case EVFILT_WRITE: 292 klist = &sysmon_power_event_queue_selinfo.sel_klist; 293 kn->kn_fop = &sysmon_power_write_filtops; 294 break; 295 296 default: 297 return (1); 298 } 299 300 simple_lock(&sysmon_power_event_queue_slock); 301 SLIST_INSERT_HEAD(klist, kn, kn_selnext); 302 simple_unlock(&sysmon_power_event_queue_slock); 303 304 return (0); 305 } 306 307 /* 308 * sysmonioctl_power: 309 * 310 * Perform a power managmenet control request. 311 */ 312 int 313 sysmonioctl_power(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p) 314 { 315 int error = 0; 316 317 switch (cmd) { 318 case POWER_IOC_GET_TYPE: 319 { 320 struct power_type *power_type = (void *) data; 321 322 strcpy(power_type->power_type, sysmon_power_type); 323 break; 324 } 325 default: 326 error = ENOTTY; 327 } 328 329 return (error); 330 } 331 332 /* 333 * sysmon_power_settype: 334 * 335 * Sets the back-end power management type. This information can 336 * be used by the power management daemon. 337 */ 338 void 339 sysmon_power_settype(const char *type) 340 { 341 342 /* 343 * Don't bother locking this; it's going to be set 344 * during autoconfiguration, and then only read from 345 * then on. 346 */ 347 strcpy(sysmon_power_type, type); 348 } 349 350 /* 351 * sysmon_pswitch_register: 352 * 353 * Register a power switch device. 354 */ 355 int 356 sysmon_pswitch_register(struct sysmon_pswitch *smpsw) 357 { 358 359 simple_lock(&sysmon_pswitch_list_slock); 360 LIST_INSERT_HEAD(&sysmon_pswitch_list, smpsw, smpsw_list); 361 simple_unlock(&sysmon_pswitch_list_slock); 362 363 return (0); 364 } 365 366 /* 367 * sysmon_pswitch_unregister: 368 * 369 * Unregister a power switch device. 370 */ 371 void 372 sysmon_pswitch_unregister(struct sysmon_pswitch *smpsw) 373 { 374 375 simple_lock(&sysmon_pswitch_list_slock); 376 LIST_REMOVE(smpsw, smpsw_list); 377 simple_unlock(&sysmon_pswitch_list_slock); 378 } 379 380 /* 381 * sysmon_pswitch_event: 382 * 383 * Register an event on a power switch device. 384 */ 385 void 386 sysmon_pswitch_event(struct sysmon_pswitch *smpsw, int event) 387 { 388 389 /* 390 * If a power management daemon is connected, then simply 391 * deliver the event to them. If not, we need to try to 392 * do something reasonable ourselves. 393 */ 394 simple_lock(&sysmon_power_event_queue_slock); 395 if (sysmon_power_daemon != NULL) { 396 power_event_t pev; 397 int rv; 398 399 pev.pev_type = POWER_EVENT_SWITCH_STATE_CHANGE; 400 pev.pev_switch.psws_state = event; 401 pev.pev_switch.psws_type = smpsw->smpsw_type; 402 strcpy(pev.pev_switch.psws_name, smpsw->smpsw_name); 403 404 rv = sysmon_queue_power_event(&pev); 405 if (rv == 0) { 406 simple_unlock(&sysmon_power_event_queue_slock); 407 printf("%s: WARNING: state change event %d lost; " 408 "queue full\n", smpsw->smpsw_name, 409 pev.pev_type); 410 return; 411 } else { 412 if (sysmon_power_event_queue_flags & PEVQ_F_WAITING) { 413 sysmon_power_event_queue_flags &= ~PEVQ_F_WAITING; 414 simple_unlock(&sysmon_power_event_queue_slock); 415 wakeup(&sysmon_power_event_queue_count); 416 } else { 417 simple_unlock(&sysmon_power_event_queue_slock); 418 } 419 selnotify(&sysmon_power_event_queue_selinfo, 0); 420 return; 421 } 422 } 423 simple_unlock(&sysmon_power_event_queue_slock); 424 425 switch (smpsw->smpsw_type) { 426 case PSWITCH_TYPE_POWER: 427 if (event != PSWITCH_EVENT_PRESSED) { 428 /* just ignore it */ 429 return; 430 } 431 432 /* 433 * Attempt a somewhat graceful shutdown of the system, 434 * as if the user has issued a reboot(2) call with 435 * RB_POWERDOWN. 436 */ 437 printf("%s: power button pressed, shutting down!\n", 438 smpsw->smpsw_name); 439 cpu_reboot(RB_POWERDOWN, NULL); 440 break; 441 442 case PSWITCH_TYPE_RESET: 443 if (event != PSWITCH_EVENT_PRESSED) { 444 /* just ignore it */ 445 return; 446 } 447 448 /* 449 * Attempt a somewhat graceful reboot of the system, 450 * as if the user had issued a reboot(2) call. 451 */ 452 printf("%s: reset button pressed, rebooting!\n", 453 smpsw->smpsw_name); 454 cpu_reboot(0, NULL); 455 break; 456 457 case PSWITCH_TYPE_SLEEP: 458 if (event != PSWITCH_EVENT_PRESSED) { 459 /* just ignore it */ 460 return; 461 } 462 463 /* 464 * Try to enter a "sleep" state. 465 */ 466 /* XXX */ 467 printf("%s: sleep button pressed.\n", smpsw->smpsw_name); 468 break; 469 470 case PSWITCH_TYPE_LID: 471 switch (event) { 472 case PSWITCH_EVENT_PRESSED: 473 /* 474 * Try to enter a "standby" state. 475 */ 476 /* XXX */ 477 printf("%s: lid closed.\n", smpsw->smpsw_name); 478 break; 479 480 case PSWITCH_EVENT_RELEASED: 481 /* 482 * Come out of "standby" state. 483 */ 484 /* XXX */ 485 printf("%s: lid opened.\n", smpsw->smpsw_name); 486 break; 487 488 default: 489 printf("%s: unknown lid switch event: %d\n", 490 smpsw->smpsw_name, event); 491 } 492 break; 493 494 default: 495 printf("%s: sysmon_pswitch_event can't handle me.\n", 496 smpsw->smpsw_name); 497 } 498 } 499