kern_pmf.c revision 1.8
1/* $NetBSD: kern_pmf.c,v 1.8 2007/12/23 22:09:39 rmind 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.8 2007/12/23 22:09:39 rmind 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_bus_resume(void) 137{ 138 int depth, maxdepth; 139 bool rv; 140 device_t curdev; 141 142 maxdepth = 0; 143 TAILQ_FOREACH(curdev, &alldevs, dv_list) { 144 if (curdev->dv_depth > maxdepth) 145 maxdepth = curdev->dv_depth; 146 } 147 ++maxdepth; 148 149 aprint_debug("Powering devices:"); 150 /* D0 handlers are run in order */ 151 depth = 0; 152 rv = true; 153 for (depth = 0; depth < maxdepth; ++depth) { 154 TAILQ_FOREACH(curdev, &alldevs, dv_list) { 155 if (!device_pmf_is_registered(curdev)) 156 continue; 157 if (device_is_active(curdev) || 158 !device_is_enabled(curdev)) 159 continue; 160 if (curdev->dv_depth != depth) 161 continue; 162 163 aprint_debug(" %s", device_xname(curdev)); 164 165 if (!device_pmf_bus_resume(curdev)) 166 aprint_debug("(failed)"); 167 } 168 } 169 aprint_debug("\n"); 170 171 return rv; 172} 173 174bool 175pmf_system_resume(void) 176{ 177 int depth, maxdepth; 178 bool rv; 179 device_t curdev, parent; 180 181 if (!pmf_check_system_drivers()) 182 return false; 183 184 maxdepth = 0; 185 TAILQ_FOREACH(curdev, &alldevs, dv_list) { 186 if (curdev->dv_depth > maxdepth) 187 maxdepth = curdev->dv_depth; 188 } 189 ++maxdepth; 190 191 aprint_debug("Resuming devices:"); 192 /* D0 handlers are run in order */ 193 depth = 0; 194 rv = true; 195 for (depth = 0; depth < maxdepth; ++depth) { 196 TAILQ_FOREACH(curdev, &alldevs, dv_list) { 197 if (device_is_active(curdev) || 198 !device_is_enabled(curdev)) 199 continue; 200 if (curdev->dv_depth != depth) 201 continue; 202 parent = device_parent(curdev); 203 if (parent != NULL && 204 !device_is_active(parent)) 205 continue; 206 207 aprint_debug(" %s", device_xname(curdev)); 208 209 if (!pmf_device_resume(curdev)) { 210 rv = false; 211 aprint_debug("(failed)"); 212 } 213 } 214 } 215 aprint_debug(".\n"); 216 217 return rv; 218} 219 220bool 221pmf_system_suspend(void) 222{ 223 int depth, maxdepth; 224 device_t curdev; 225 226 if (!pmf_check_system_drivers()) 227 return false; 228 229 /* 230 * Flush buffers only if the shutdown didn't do so 231 * already and if there was no panic. 232 */ 233 if (doing_shutdown == 0 && panicstr == NULL) { 234 printf("Flushing disk caches: "); 235 sys_sync(NULL, NULL, NULL); 236 if (buf_syncwait() != 0) 237 printf("giving up\n"); 238 else 239 printf("done\n"); 240 } 241 242 aprint_debug("Suspending devices:"); 243 244 maxdepth = 0; 245 TAILQ_FOREACH(curdev, &alldevs, dv_list) { 246 if (curdev->dv_depth > maxdepth) 247 maxdepth = curdev->dv_depth; 248 } 249 250 for (depth = maxdepth; depth >= 0; --depth) { 251 TAILQ_FOREACH_REVERSE(curdev, &alldevs, devicelist, dv_list) { 252 if (curdev->dv_depth != depth) 253 continue; 254 if (!device_is_active(curdev)) 255 continue; 256 257 aprint_debug(" %s", device_xname(curdev)); 258 259 /* XXX joerg check return value and abort suspend */ 260 if (!pmf_device_suspend(curdev)) 261 aprint_debug("(failed)"); 262 } 263 } 264 265 aprint_debug(".\n"); 266 267 return true; 268} 269 270void 271pmf_system_shutdown(void) 272{ 273 int depth, maxdepth; 274 device_t curdev; 275 276 if (!pmf_check_system_drivers()) 277 delay(2000000); 278 279 aprint_debug("Shutting down devices:"); 280 281 maxdepth = 0; 282 TAILQ_FOREACH(curdev, &alldevs, dv_list) { 283 if (curdev->dv_depth > maxdepth) 284 maxdepth = curdev->dv_depth; 285 } 286 287 for (depth = maxdepth; depth >= 0; --depth) { 288 TAILQ_FOREACH_REVERSE(curdev, &alldevs, devicelist, dv_list) { 289 if (curdev->dv_depth != depth) 290 continue; 291 if (!device_is_active(curdev)) 292 continue; 293 294 aprint_debug(" %s", device_xname(curdev)); 295 296 if (!device_pmf_is_registered(curdev)) 297 continue; 298 if (!device_pmf_class_suspend(curdev)) { 299 aprint_debug("(failed)"); 300 continue; 301 } 302 if (!device_pmf_driver_suspend(curdev)) { 303 aprint_debug("(failed)"); 304 continue; 305 } 306 } 307 } 308 309 aprint_debug(".\n"); 310} 311 312bool 313pmf_set_platform(const char *key, const char *value) 314{ 315 if (pmf_platform == NULL) 316 pmf_platform = prop_dictionary_create(); 317 if (pmf_platform == NULL) 318 return false; 319 320 return prop_dictionary_set_cstring(pmf_platform, key, value); 321} 322 323const char * 324pmf_get_platform(const char *key) 325{ 326 const char *value; 327 328 if (pmf_platform == NULL) 329 return NULL; 330 331 if (!prop_dictionary_get_cstring_nocopy(pmf_platform, key, &value)) 332 return NULL; 333 334 return value; 335} 336 337bool 338pmf_device_register(device_t dev, 339 bool (*suspend)(device_t), bool (*resume)(device_t)) 340{ 341 device_pmf_driver_register(dev, suspend, resume); 342 343 if (!device_pmf_driver_child_register(dev)) { 344 device_pmf_driver_deregister(dev); 345 return false; 346 } 347 348 return true; 349} 350 351void 352pmf_device_deregister(device_t dev) 353{ 354 device_pmf_class_deregister(dev); 355 device_pmf_bus_deregister(dev); 356 device_pmf_driver_deregister(dev); 357} 358 359bool 360pmf_device_suspend(device_t dev) 361{ 362 PMF_TRANSITION_PRINTF(("%s: suspend enter\n", device_xname(dev))); 363 if (!device_pmf_is_registered(dev)) 364 return false; 365 PMF_TRANSITION_PRINTF2(1, ("%s: class suspend\n", device_xname(dev))); 366 if (!device_pmf_class_suspend(dev)) 367 return false; 368 PMF_TRANSITION_PRINTF2(1, ("%s: driver suspend\n", device_xname(dev))); 369 if (!device_pmf_driver_suspend(dev)) 370 return false; 371 PMF_TRANSITION_PRINTF2(1, ("%s: bus suspend\n", device_xname(dev))); 372 if (!device_pmf_bus_suspend(dev)) 373 return false; 374 PMF_TRANSITION_PRINTF(("%s: suspend exit\n", device_xname(dev))); 375 return true; 376} 377 378bool 379pmf_device_resume(device_t dev) 380{ 381 PMF_TRANSITION_PRINTF(("%s: resume enter\n", device_xname(dev))); 382 if (!device_pmf_is_registered(dev)) 383 return false; 384 PMF_TRANSITION_PRINTF2(1, ("%s: bus resume\n", device_xname(dev))); 385 if (!device_pmf_bus_resume(dev)) 386 return false; 387 PMF_TRANSITION_PRINTF2(1, ("%s: driver resume\n", device_xname(dev))); 388 if (!device_pmf_driver_resume(dev)) 389 return false; 390 PMF_TRANSITION_PRINTF2(1, ("%s: class resume\n", device_xname(dev))); 391 if (!device_pmf_class_resume(dev)) 392 return false; 393 PMF_TRANSITION_PRINTF(("%s: resume exit\n", device_xname(dev))); 394 return true; 395} 396 397bool 398pmf_device_recursive_suspend(device_t dv) 399{ 400 device_t curdev; 401 402 if (!device_is_active(dv)) 403 return true; 404 405 TAILQ_FOREACH(curdev, &alldevs, dv_list) { 406 if (device_parent(curdev) != dv) 407 continue; 408 if (!pmf_device_recursive_suspend(curdev)) 409 return false; 410 } 411 412 return pmf_device_suspend(dv); 413} 414 415bool 416pmf_device_recursive_resume(device_t dv) 417{ 418 device_t parent; 419 420 if (device_is_active(dv)) 421 return true; 422 423 parent = device_parent(dv); 424 if (parent != NULL) { 425 if (!pmf_device_recursive_resume(parent)) 426 return false; 427 } 428 429 return pmf_device_resume(dv); 430} 431 432bool 433pmf_device_resume_subtree(device_t dv) 434{ 435 device_t curdev; 436 437 if (!pmf_device_recursive_resume(dv)) 438 return false; 439 440 TAILQ_FOREACH(curdev, &alldevs, dv_list) { 441 if (device_parent(curdev) != dv) 442 continue; 443 if (!pmf_device_resume_subtree(curdev)) 444 return false; 445 } 446 return true; 447} 448 449#include <net/if.h> 450 451static bool 452pmf_class_network_suspend(device_t dev) 453{ 454 struct ifnet *ifp = device_pmf_class_private(dev); 455 int s; 456 457 s = splnet(); 458 (*ifp->if_stop)(ifp, 1); 459 splx(s); 460 461 return true; 462} 463 464static bool 465pmf_class_network_resume(device_t dev) 466{ 467 struct ifnet *ifp = device_pmf_class_private(dev); 468 int s; 469 470 s = splnet(); 471 if (ifp->if_flags & IFF_UP) { 472 ifp->if_flags &= ~IFF_RUNNING; 473 (*ifp->if_init)(ifp); 474 (*ifp->if_start)(ifp); 475 } 476 splx(s); 477 478 return true; 479} 480 481void 482pmf_class_network_register(device_t dev, struct ifnet *ifp) 483{ 484 device_pmf_class_register(dev, ifp, pmf_class_network_suspend, 485 pmf_class_network_resume, NULL); 486} 487 488bool 489pmf_event_inject(device_t dv, pmf_generic_event_t ev) 490{ 491 pmf_event_workitem_t *pew; 492 493 pew = malloc(sizeof(pmf_event_workitem_t), M_TEMP, M_NOWAIT); 494 if (pew == NULL) { 495 PMF_EVENT_PRINTF(("%s: PMF event %d dropped (no memory)\n", 496 dv ? device_xname(dv) : "<anonymous>", ev)); 497 return false; 498 } 499 500 pew->pew_event = ev; 501 pew->pew_device = dv; 502 503 workqueue_enqueue(pmf_event_workqueue, (void *)pew, NULL); 504 PMF_EVENT_PRINTF(("%s: PMF event %d injected\n", 505 dv ? device_xname(dv) : "<anonymous>", ev)); 506 507 return true; 508} 509 510bool 511pmf_event_register(device_t dv, pmf_generic_event_t ev, 512 void (*handler)(device_t), bool global) 513{ 514 pmf_event_handler_t *event; 515 516 event = malloc(sizeof(*event), M_DEVBUF, M_WAITOK); 517 event->pmf_event = ev; 518 event->pmf_handler = handler; 519 event->pmf_device = dv; 520 event->pmf_global = global; 521 TAILQ_INSERT_TAIL(&pmf_all_events, event, pmf_link); 522 523 return true; 524} 525 526void 527pmf_event_deregister(device_t dv, pmf_generic_event_t ev, 528 void (*handler)(device_t), bool global) 529{ 530 pmf_event_handler_t *event; 531 532 while (!TAILQ_EMPTY(&pmf_all_events)) { 533 event = TAILQ_FIRST(&pmf_all_events); 534 if (event->pmf_event != ev) 535 continue; 536 if (event->pmf_device != dv) 537 continue; 538 if (event->pmf_global != global) 539 continue; 540 if (event->pmf_handler != handler) 541 continue; 542 TAILQ_REMOVE(&pmf_all_events, event, pmf_link); 543 free(event, M_WAITOK); 544 } 545} 546 547struct display_class_softc { 548 TAILQ_ENTRY(display_class_softc) dc_link; 549 device_t dc_dev; 550}; 551 552static TAILQ_HEAD(, display_class_softc) all_displays; 553static callout_t global_idle_counter; 554static int idle_timeout = 30; 555 556static void 557input_idle(void *dummy) 558{ 559 PMF_IDLE_PRINTF(("Input idle handler called\n")); 560 pmf_event_inject(NULL, PMFE_DISPLAY_OFF); 561} 562 563static void 564input_activity_handler(device_t dv, devactive_t type) 565{ 566 if (!TAILQ_EMPTY(&all_displays)) 567 callout_schedule(&global_idle_counter, idle_timeout * hz); 568} 569 570static void 571pmf_class_input_deregister(device_t dv) 572{ 573 device_active_deregister(dv, input_activity_handler); 574} 575 576bool 577pmf_class_input_register(device_t dv) 578{ 579 if (!device_active_register(dv, input_activity_handler)) 580 return false; 581 582 device_pmf_class_register(dv, NULL, NULL, NULL, 583 pmf_class_input_deregister); 584 585 return true; 586} 587 588static void 589pmf_class_display_deregister(device_t dv) 590{ 591 struct display_class_softc *sc = device_pmf_class_private(dv); 592 int s; 593 594 s = splsoftclock(); 595 TAILQ_REMOVE(&all_displays, sc, dc_link); 596 if (TAILQ_EMPTY(&all_displays)) 597 callout_stop(&global_idle_counter); 598 splx(s); 599 600 free(sc, M_DEVBUF); 601} 602 603bool 604pmf_class_display_register(device_t dv) 605{ 606 struct display_class_softc *sc; 607 int s; 608 609 sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK); 610 611 s = splsoftclock(); 612 if (TAILQ_EMPTY(&all_displays)) 613 callout_schedule(&global_idle_counter, idle_timeout * hz); 614 615 TAILQ_INSERT_HEAD(&all_displays, sc, dc_link); 616 splx(s); 617 618 device_pmf_class_register(dv, sc, NULL, NULL, 619 pmf_class_display_deregister); 620 621 return true; 622} 623 624void 625pmf_init(void) 626{ 627 int err; 628 629 KASSERT(pmf_event_workqueue == NULL); 630 err = workqueue_create(&pmf_event_workqueue, "pmfevent", 631 pmf_event_worker, NULL, PRI_NONE, IPL_VM, 0); 632 if (err) 633 panic("couldn't create pmfevent workqueue"); 634 635 callout_init(&global_idle_counter, 0); 636 callout_setfunc(&global_idle_counter, input_idle, NULL); 637} 638