kern_pmf.c revision 1.4
1/* $NetBSD: kern_pmf.c,v 1.4 2007/12/11 01:00:45 jmcneill Exp $ */ 2 3/*- 4 * Copyright (c) 2007 Jared D. McNeill <jmcneill@invisible.ca> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. All advertising materials mentioning features or use of this software 16 * must display the following acknowledgement: 17 * This product includes software developed by Jared D. McNeill. 18 * 4. Neither the name of The NetBSD Foundation nor the names of its 19 * contributors may be used to endorse or promote products derived 20 * from this software without specific prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 23 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 24 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 25 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 26 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 28 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 29 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 * POSSIBILITY OF SUCH DAMAGE. 33 */ 34 35#include <sys/cdefs.h> 36__KERNEL_RCSID(0, "$NetBSD: kern_pmf.c,v 1.4 2007/12/11 01:00:45 jmcneill Exp $"); 37 38#include <sys/types.h> 39#include <sys/param.h> 40#include <sys/malloc.h> 41#include <sys/buf.h> 42#include <sys/callout.h> 43#include <sys/kernel.h> 44#include <sys/device.h> 45#include <sys/pmf.h> 46#include <sys/queue.h> 47#include <sys/syscallargs.h> /* for sys_sync */ 48#include <sys/workqueue.h> 49#include <prop/proplib.h> 50 51#ifdef PMF_DEBUG 52int pmf_debug_event; 53int pmf_debug_idle; 54int pmf_debug_transition; 55 56#define PMF_EVENT_PRINTF(x) if (pmf_debug_event) printf x 57#define PMF_IDLE_PRINTF(x) if (pmf_debug_idle) printf x 58#define PMF_TRANSITION_PRINTF(x) if (pmf_debug_transition) printf x 59#define PMF_TRANSITION_PRINTF2(y,x) if (pmf_debug_transition>y) printf x 60#else 61#define PMF_EVENT_PRINTF(x) do { } while (0) 62#define PMF_IDLE_PRINTF(x) do { } while (0) 63#define PMF_TRANSITION_PRINTF(x) do { } while (0) 64#define PMF_TRANSITION_PRINTF2(y,x) do { } while (0) 65#endif 66 67/* #define PMF_DEBUG */ 68 69MALLOC_DEFINE(M_PMF, "pmf", "device pmf messaging memory"); 70 71static prop_dictionary_t pmf_platform = NULL; 72static struct workqueue *pmf_event_workqueue; 73 74typedef struct pmf_event_handler { 75 TAILQ_ENTRY(pmf_event_handler) pmf_link; 76 pmf_generic_event_t pmf_event; 77 void (*pmf_handler)(device_t); 78 device_t pmf_device; 79 bool pmf_global; 80} pmf_event_handler_t; 81 82static TAILQ_HEAD(, pmf_event_handler) pmf_all_events = 83 TAILQ_HEAD_INITIALIZER(pmf_all_events); 84 85typedef struct pmf_event_workitem { 86 struct work pew_work; 87 pmf_generic_event_t pew_event; 88 device_t pew_device; 89} pmf_event_workitem_t; 90 91static void 92pmf_event_worker(struct work *wk, void *dummy) 93{ 94 pmf_event_workitem_t *pew; 95 pmf_event_handler_t *event; 96 97 pew = (void *)wk; 98 KASSERT(wk == &pew->pew_work); 99 KASSERT(pew != NULL); 100 101 TAILQ_FOREACH(event, &pmf_all_events, pmf_link) { 102 if (event->pmf_event != pew->pew_event) 103 continue; 104 if (event->pmf_device != pew->pew_device || event->pmf_global) 105 (*event->pmf_handler)(event->pmf_device); 106 } 107 108 free(pew, M_TEMP); 109 110 return; 111} 112 113static bool 114pmf_check_system_drivers(void) 115{ 116 device_t curdev; 117 bool unsupported_devs; 118 119 unsupported_devs = false; 120 TAILQ_FOREACH(curdev, &alldevs, dv_list) { 121 if (device_pmf_is_registered(curdev)) 122 continue; 123 if (!unsupported_devs) 124 printf("Devices without power management support:"); 125 printf(" %s", device_xname(curdev)); 126 unsupported_devs = true; 127 } 128 if (unsupported_devs) { 129 printf("\n"); 130 return false; 131 } 132 return true; 133} 134 135bool 136pmf_system_resume(void) 137{ 138 int depth, maxdepth; 139 bool rv; 140 device_t curdev, parent; 141 142 if (!pmf_check_system_drivers()) 143 return false; 144 145 maxdepth = 0; 146 TAILQ_FOREACH(curdev, &alldevs, dv_list) { 147 if (curdev->dv_depth > maxdepth) 148 maxdepth = curdev->dv_depth; 149 } 150 ++maxdepth; 151 152 aprint_debug("Resuming devices:"); 153 /* D0 handlers are run in order */ 154 depth = 0; 155 rv = true; 156 for (depth = 0; depth < maxdepth; ++depth) { 157 TAILQ_FOREACH(curdev, &alldevs, dv_list) { 158 if (device_is_active(curdev) || 159 !device_is_enabled(curdev)) 160 continue; 161 if (curdev->dv_depth != depth) 162 continue; 163 parent = device_parent(curdev); 164 if (parent != NULL && 165 !device_is_active(parent)) 166 continue; 167 168 aprint_debug(" %s", device_xname(curdev)); 169 170 if (!pmf_device_resume(curdev)) { 171 rv = false; 172 aprint_debug("(failed)"); 173 } 174 } 175 } 176 aprint_debug(".\n"); 177 178 return rv; 179} 180 181bool 182pmf_system_suspend(void) 183{ 184 int depth, maxdepth; 185 device_t curdev; 186 187 if (!pmf_check_system_drivers()) 188 return false; 189 190 /* 191 * Flush buffers only if the shutdown didn't do so 192 * already and if there was no panic. 193 */ 194 if (doing_shutdown == 0 && panicstr == NULL) { 195 printf("Flushing disk caches: "); 196 sys_sync(NULL, NULL, NULL); 197 if (buf_syncwait() != 0) 198 printf("giving up\n"); 199 else 200 printf("done\n"); 201 } 202 203 aprint_debug("Suspending devices:"); 204 205 maxdepth = 0; 206 TAILQ_FOREACH(curdev, &alldevs, dv_list) { 207 if (curdev->dv_depth > maxdepth) 208 maxdepth = curdev->dv_depth; 209 } 210 211 for (depth = maxdepth; depth >= 0; --depth) { 212 TAILQ_FOREACH_REVERSE(curdev, &alldevs, devicelist, dv_list) { 213 if (curdev->dv_depth != depth) 214 continue; 215 if (!device_is_active(curdev)) 216 continue; 217 218 aprint_debug(" %s", device_xname(curdev)); 219 220 /* XXX joerg check return value and abort suspend */ 221 if (!pmf_device_suspend(curdev)) 222 aprint_debug("(failed)"); 223 } 224 } 225 226 aprint_debug(".\n"); 227 228 return true; 229} 230 231void 232pmf_system_shutdown(void) 233{ 234 int depth, maxdepth; 235 device_t curdev; 236 237 if (!pmf_check_system_drivers()) 238 delay(2000000); 239 240 aprint_debug("Shutting down devices:"); 241 242 maxdepth = 0; 243 TAILQ_FOREACH(curdev, &alldevs, dv_list) { 244 if (curdev->dv_depth > maxdepth) 245 maxdepth = curdev->dv_depth; 246 } 247 248 for (depth = maxdepth; depth >= 0; --depth) { 249 TAILQ_FOREACH_REVERSE(curdev, &alldevs, devicelist, dv_list) { 250 if (curdev->dv_depth != depth) 251 continue; 252 if (!device_is_active(curdev)) 253 continue; 254 255 aprint_debug(" %s", device_xname(curdev)); 256 257 if (!device_pmf_is_registered(curdev)) 258 continue; 259 if (!device_pmf_class_suspend(curdev)) { 260 aprint_debug("(failed)"); 261 continue; 262 } 263 if (!device_pmf_driver_suspend(curdev)) { 264 aprint_debug("(failed)"); 265 continue; 266 } 267 } 268 } 269 270 aprint_debug(".\n"); 271} 272 273bool 274pmf_set_platform(const char *key, const char *value) 275{ 276 if (pmf_platform == NULL) 277 pmf_platform = prop_dictionary_create(); 278 if (pmf_platform == NULL) 279 return false; 280 281 return prop_dictionary_set_cstring(pmf_platform, key, value); 282} 283 284const char * 285pmf_get_platform(const char *key) 286{ 287 const char *value; 288 289 if (pmf_platform == NULL) 290 return NULL; 291 292 if (!prop_dictionary_get_cstring_nocopy(pmf_platform, key, &value)) 293 return NULL; 294 295 return value; 296} 297 298bool 299pmf_device_register(device_t dev, 300 bool (*suspend)(device_t), bool (*resume)(device_t)) 301{ 302 device_pmf_driver_register(dev, suspend, resume); 303 304 if (!device_pmf_driver_child_register(dev)) { 305 device_pmf_driver_deregister(dev); 306 return false; 307 } 308 309 return true; 310} 311 312void 313pmf_device_deregister(device_t dev) 314{ 315 device_pmf_class_deregister(dev); 316 device_pmf_bus_deregister(dev); 317 device_pmf_driver_deregister(dev); 318} 319 320bool 321pmf_device_suspend(device_t dev) 322{ 323 PMF_TRANSITION_PRINTF(("%s: suspend enter\n", device_xname(dev))); 324 if (!device_pmf_is_registered(dev)) 325 return false; 326 PMF_TRANSITION_PRINTF2(1, ("%s: class suspend\n", device_xname(dev))); 327 if (!device_pmf_class_suspend(dev)) 328 return false; 329 PMF_TRANSITION_PRINTF2(1, ("%s: driver suspend\n", device_xname(dev))); 330 if (!device_pmf_driver_suspend(dev)) 331 return false; 332 PMF_TRANSITION_PRINTF2(1, ("%s: bus suspend\n", device_xname(dev))); 333 if (!device_pmf_bus_suspend(dev)) 334 return false; 335 PMF_TRANSITION_PRINTF(("%s: suspend exit\n", device_xname(dev))); 336 return true; 337} 338 339bool 340pmf_device_resume(device_t dev) 341{ 342 PMF_TRANSITION_PRINTF(("%s: resume enter\n", device_xname(dev))); 343 if (!device_pmf_is_registered(dev)) 344 return false; 345 PMF_TRANSITION_PRINTF2(1, ("%s: bus resume\n", device_xname(dev))); 346 if (!device_pmf_bus_resume(dev)) 347 return false; 348 PMF_TRANSITION_PRINTF2(1, ("%s: driver resume\n", device_xname(dev))); 349 if (!device_pmf_driver_resume(dev)) 350 return false; 351 PMF_TRANSITION_PRINTF2(1, ("%s: class resume\n", device_xname(dev))); 352 if (!device_pmf_class_resume(dev)) 353 return false; 354 PMF_TRANSITION_PRINTF(("%s: resume exit\n", device_xname(dev))); 355 return true; 356} 357 358bool 359pmf_device_recursive_suspend(device_t dv) 360{ 361 device_t curdev; 362 363 if (!device_is_active(dv)) 364 return true; 365 366 TAILQ_FOREACH(curdev, &alldevs, dv_list) { 367 if (device_parent(curdev) != dv) 368 continue; 369 if (!pmf_device_recursive_suspend(curdev)) 370 return false; 371 } 372 373 return pmf_device_suspend(dv); 374} 375 376bool 377pmf_device_recursive_resume(device_t dv) 378{ 379 device_t parent; 380 381 if (device_is_active(dv)) 382 return true; 383 384 parent = device_parent(dv); 385 if (parent != NULL) { 386 if (!pmf_device_recursive_resume(parent)) 387 return false; 388 } 389 390 return pmf_device_resume(dv); 391} 392 393bool 394pmf_device_resume_subtree(device_t dv) 395{ 396 device_t curdev; 397 398 if (!pmf_device_recursive_resume(dv)) 399 return false; 400 401 TAILQ_FOREACH(curdev, &alldevs, dv_list) { 402 if (device_parent(curdev) != dv) 403 continue; 404 if (!pmf_device_resume_subtree(curdev)) 405 return false; 406 } 407 return true; 408} 409 410#include <net/if.h> 411 412static bool 413pmf_class_network_suspend(device_t dev) 414{ 415 struct ifnet *ifp = device_pmf_class_private(dev); 416 int s; 417 418 s = splnet(); 419 (*ifp->if_stop)(ifp, 1); 420 splx(s); 421 422 return true; 423} 424 425static bool 426pmf_class_network_resume(device_t dev) 427{ 428 struct ifnet *ifp = device_pmf_class_private(dev); 429 int s; 430 431 s = splnet(); 432 if (ifp->if_flags & IFF_UP) { 433 ifp->if_flags &= ~IFF_RUNNING; 434 (*ifp->if_init)(ifp); 435 (*ifp->if_start)(ifp); 436 } 437 splx(s); 438 439 return true; 440} 441 442void 443pmf_class_network_register(device_t dev, struct ifnet *ifp) 444{ 445 device_pmf_class_register(dev, ifp, pmf_class_network_suspend, 446 pmf_class_network_resume, NULL); 447} 448 449bool 450pmf_event_inject(device_t dv, pmf_generic_event_t ev) 451{ 452 pmf_event_workitem_t *pew; 453 454 pew = malloc(sizeof(pmf_event_workitem_t), M_TEMP, M_NOWAIT); 455 if (pew == NULL) { 456 PMF_EVENT_PRINTF(("%s: PMF event %d dropped (no memory)\n", 457 dv ? device_xname(dv) : "<anonymous>", ev)); 458 return false; 459 } 460 461 pew->pew_event = ev; 462 pew->pew_device = dv; 463 464 workqueue_enqueue(pmf_event_workqueue, (void *)pew, NULL); 465 PMF_EVENT_PRINTF(("%s: PMF event %d injected\n", 466 dv ? device_xname(dv) : "<anonymous>", ev)); 467 468 return true; 469} 470 471bool 472pmf_event_register(device_t dv, pmf_generic_event_t ev, 473 void (*handler)(device_t), bool global) 474{ 475 pmf_event_handler_t *event; 476 477 event = malloc(sizeof(*event), M_DEVBUF, M_WAITOK); 478 event->pmf_event = ev; 479 event->pmf_handler = handler; 480 event->pmf_device = dv; 481 event->pmf_global = global; 482 TAILQ_INSERT_TAIL(&pmf_all_events, event, pmf_link); 483 484 return true; 485} 486 487void 488pmf_event_deregister(device_t dv, pmf_generic_event_t ev, 489 void (*handler)(device_t), bool global) 490{ 491 pmf_event_handler_t *event; 492 493 TAILQ_FOREACH(event, &pmf_all_events, pmf_link) { 494 if (event->pmf_event != ev) 495 continue; 496 if (event->pmf_device != dv) 497 continue; 498 if (event->pmf_global != global) 499 continue; 500 if (event->pmf_handler != handler) 501 continue; 502 TAILQ_REMOVE(&pmf_all_events, event, pmf_link); 503 free(event, M_WAITOK); 504 } 505} 506 507struct display_class_softc { 508 TAILQ_ENTRY(display_class_softc) dc_link; 509 device_t dc_dev; 510}; 511 512static TAILQ_HEAD(, display_class_softc) all_displays; 513static callout_t global_idle_counter; 514static int idle_timeout = 30; 515 516static void 517input_idle(void *dummy) 518{ 519 PMF_IDLE_PRINTF(("Input idle handler called\n")); 520 pmf_event_inject(NULL, PMFE_DISPLAY_OFF); 521} 522 523static void 524input_activity_handler(device_t dv, devactive_t type) 525{ 526 if (!TAILQ_EMPTY(&all_displays)) 527 callout_schedule(&global_idle_counter, idle_timeout * hz); 528} 529 530static void 531pmf_class_input_deregister(device_t dv) 532{ 533 device_active_deregister(dv, input_activity_handler); 534} 535 536bool 537pmf_class_input_register(device_t dv) 538{ 539 if (!device_active_register(dv, input_activity_handler)) 540 return false; 541 542 device_pmf_class_register(dv, NULL, NULL, NULL, 543 pmf_class_input_deregister); 544 545 return true; 546} 547 548static void 549pmf_class_display_deregister(device_t dv) 550{ 551 struct display_class_softc *sc = device_pmf_class_private(dv); 552 int s; 553 554 s = splsoftclock(); 555 TAILQ_REMOVE(&all_displays, sc, dc_link); 556 if (TAILQ_EMPTY(&all_displays)) 557 callout_stop(&global_idle_counter); 558 splx(s); 559 560 free(sc, M_DEVBUF); 561} 562 563bool 564pmf_class_display_register(device_t dv) 565{ 566 struct display_class_softc *sc; 567 int s; 568 569 sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK); 570 571 s = splsoftclock(); 572 if (TAILQ_EMPTY(&all_displays)) 573 callout_schedule(&global_idle_counter, idle_timeout * hz); 574 575 TAILQ_INSERT_HEAD(&all_displays, sc, dc_link); 576 splx(s); 577 578 device_pmf_class_register(dv, sc, NULL, NULL, 579 pmf_class_display_deregister); 580 581 return true; 582} 583 584void 585pmf_init(void) 586{ 587 int err; 588 589 KASSERT(pmf_event_workqueue == NULL); 590 err = workqueue_create(&pmf_event_workqueue, "pmfevent", 591 pmf_event_worker, NULL, PRI_IDLE, IPL_VM, 0); 592 if (err) 593 panic("couldn't create pmfevent workqueue"); 594 595 callout_init(&global_idle_counter, 0); 596 callout_setfunc(&global_idle_counter, input_idle, NULL); 597} 598