kern_pmf.c revision 1.10
1/* $NetBSD: kern_pmf.c,v 1.10 2007/12/27 16:03:10 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.10 2007/12/27 16:03:10 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_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 aprint_debug("Shutting down devices:"); 277 278 maxdepth = 0; 279 TAILQ_FOREACH(curdev, &alldevs, dv_list) { 280 if (curdev->dv_depth > maxdepth) 281 maxdepth = curdev->dv_depth; 282 } 283 284 for (depth = maxdepth; depth >= 0; --depth) { 285 TAILQ_FOREACH_REVERSE(curdev, &alldevs, devicelist, dv_list) { 286 if (curdev->dv_depth != depth) 287 continue; 288 if (!device_is_active(curdev)) 289 continue; 290 291 aprint_debug(" %s", device_xname(curdev)); 292 293 if (!device_pmf_is_registered(curdev)) 294 continue; 295 if (!device_pmf_class_suspend(curdev)) { 296 aprint_debug("(failed)"); 297 continue; 298 } 299 if (!device_pmf_driver_suspend(curdev)) { 300 aprint_debug("(failed)"); 301 continue; 302 } 303 } 304 } 305 306 aprint_debug(".\n"); 307} 308 309bool 310pmf_set_platform(const char *key, const char *value) 311{ 312 if (pmf_platform == NULL) 313 pmf_platform = prop_dictionary_create(); 314 if (pmf_platform == NULL) 315 return false; 316 317 return prop_dictionary_set_cstring(pmf_platform, key, value); 318} 319 320const char * 321pmf_get_platform(const char *key) 322{ 323 const char *value; 324 325 if (pmf_platform == NULL) 326 return NULL; 327 328 if (!prop_dictionary_get_cstring_nocopy(pmf_platform, key, &value)) 329 return NULL; 330 331 return value; 332} 333 334bool 335pmf_device_register(device_t dev, 336 bool (*suspend)(device_t), bool (*resume)(device_t)) 337{ 338 device_pmf_driver_register(dev, suspend, resume); 339 340 if (!device_pmf_driver_child_register(dev)) { 341 device_pmf_driver_deregister(dev); 342 return false; 343 } 344 345 return true; 346} 347 348void 349pmf_device_deregister(device_t dev) 350{ 351 device_pmf_class_deregister(dev); 352 device_pmf_bus_deregister(dev); 353 device_pmf_driver_deregister(dev); 354} 355 356bool 357pmf_device_suspend(device_t dev) 358{ 359 PMF_TRANSITION_PRINTF(("%s: suspend enter\n", device_xname(dev))); 360 if (!device_pmf_is_registered(dev)) 361 return false; 362 PMF_TRANSITION_PRINTF2(1, ("%s: class suspend\n", device_xname(dev))); 363 if (!device_pmf_class_suspend(dev)) 364 return false; 365 PMF_TRANSITION_PRINTF2(1, ("%s: driver suspend\n", device_xname(dev))); 366 if (!device_pmf_driver_suspend(dev)) 367 return false; 368 PMF_TRANSITION_PRINTF2(1, ("%s: bus suspend\n", device_xname(dev))); 369 if (!device_pmf_bus_suspend(dev)) 370 return false; 371 PMF_TRANSITION_PRINTF(("%s: suspend exit\n", device_xname(dev))); 372 return true; 373} 374 375bool 376pmf_device_resume(device_t dev) 377{ 378 PMF_TRANSITION_PRINTF(("%s: resume enter\n", device_xname(dev))); 379 if (!device_pmf_is_registered(dev)) 380 return false; 381 PMF_TRANSITION_PRINTF2(1, ("%s: bus resume\n", device_xname(dev))); 382 if (!device_pmf_bus_resume(dev)) 383 return false; 384 PMF_TRANSITION_PRINTF2(1, ("%s: driver resume\n", device_xname(dev))); 385 if (!device_pmf_driver_resume(dev)) 386 return false; 387 PMF_TRANSITION_PRINTF2(1, ("%s: class resume\n", device_xname(dev))); 388 if (!device_pmf_class_resume(dev)) 389 return false; 390 PMF_TRANSITION_PRINTF(("%s: resume exit\n", device_xname(dev))); 391 return true; 392} 393 394bool 395pmf_device_recursive_suspend(device_t dv) 396{ 397 device_t curdev; 398 399 if (!device_is_active(dv)) 400 return true; 401 402 TAILQ_FOREACH(curdev, &alldevs, dv_list) { 403 if (device_parent(curdev) != dv) 404 continue; 405 if (!pmf_device_recursive_suspend(curdev)) 406 return false; 407 } 408 409 return pmf_device_suspend(dv); 410} 411 412bool 413pmf_device_recursive_resume(device_t dv) 414{ 415 device_t parent; 416 417 if (device_is_active(dv)) 418 return true; 419 420 parent = device_parent(dv); 421 if (parent != NULL) { 422 if (!pmf_device_recursive_resume(parent)) 423 return false; 424 } 425 426 return pmf_device_resume(dv); 427} 428 429bool 430pmf_device_resume_subtree(device_t dv) 431{ 432 device_t curdev; 433 434 if (!pmf_device_recursive_resume(dv)) 435 return false; 436 437 TAILQ_FOREACH(curdev, &alldevs, dv_list) { 438 if (device_parent(curdev) != dv) 439 continue; 440 if (!pmf_device_resume_subtree(curdev)) 441 return false; 442 } 443 return true; 444} 445 446#include <net/if.h> 447 448static bool 449pmf_class_network_suspend(device_t dev) 450{ 451 struct ifnet *ifp = device_pmf_class_private(dev); 452 int s; 453 454 s = splnet(); 455 (*ifp->if_stop)(ifp, 1); 456 splx(s); 457 458 return true; 459} 460 461static bool 462pmf_class_network_resume(device_t dev) 463{ 464 struct ifnet *ifp = device_pmf_class_private(dev); 465 int s; 466 467 s = splnet(); 468 if (ifp->if_flags & IFF_UP) { 469 ifp->if_flags &= ~IFF_RUNNING; 470 (*ifp->if_init)(ifp); 471 (*ifp->if_start)(ifp); 472 } 473 splx(s); 474 475 return true; 476} 477 478void 479pmf_class_network_register(device_t dev, struct ifnet *ifp) 480{ 481 device_pmf_class_register(dev, ifp, pmf_class_network_suspend, 482 pmf_class_network_resume, NULL); 483} 484 485bool 486pmf_event_inject(device_t dv, pmf_generic_event_t ev) 487{ 488 pmf_event_workitem_t *pew; 489 490 pew = malloc(sizeof(pmf_event_workitem_t), M_TEMP, M_NOWAIT); 491 if (pew == NULL) { 492 PMF_EVENT_PRINTF(("%s: PMF event %d dropped (no memory)\n", 493 dv ? device_xname(dv) : "<anonymous>", ev)); 494 return false; 495 } 496 497 pew->pew_event = ev; 498 pew->pew_device = dv; 499 500 workqueue_enqueue(pmf_event_workqueue, (void *)pew, NULL); 501 PMF_EVENT_PRINTF(("%s: PMF event %d injected\n", 502 dv ? device_xname(dv) : "<anonymous>", ev)); 503 504 return true; 505} 506 507bool 508pmf_event_register(device_t dv, pmf_generic_event_t ev, 509 void (*handler)(device_t), bool global) 510{ 511 pmf_event_handler_t *event; 512 513 event = malloc(sizeof(*event), M_DEVBUF, M_WAITOK); 514 event->pmf_event = ev; 515 event->pmf_handler = handler; 516 event->pmf_device = dv; 517 event->pmf_global = global; 518 TAILQ_INSERT_TAIL(&pmf_all_events, event, pmf_link); 519 520 return true; 521} 522 523void 524pmf_event_deregister(device_t dv, pmf_generic_event_t ev, 525 void (*handler)(device_t), bool global) 526{ 527 pmf_event_handler_t *event; 528 529 TAILQ_FOREACH(event, &pmf_all_events, pmf_link) { 530 if (event->pmf_event != ev) 531 continue; 532 if (event->pmf_device != dv) 533 continue; 534 if (event->pmf_global != global) 535 continue; 536 if (event->pmf_handler != handler) 537 continue; 538 TAILQ_REMOVE(&pmf_all_events, event, pmf_link); 539 free(event, M_WAITOK); 540 return; 541 } 542} 543 544struct display_class_softc { 545 TAILQ_ENTRY(display_class_softc) dc_link; 546 device_t dc_dev; 547}; 548 549static TAILQ_HEAD(, display_class_softc) all_displays; 550static callout_t global_idle_counter; 551static int idle_timeout = 30; 552 553static void 554input_idle(void *dummy) 555{ 556 PMF_IDLE_PRINTF(("Input idle handler called\n")); 557 pmf_event_inject(NULL, PMFE_DISPLAY_OFF); 558} 559 560static void 561input_activity_handler(device_t dv, devactive_t type) 562{ 563 if (!TAILQ_EMPTY(&all_displays)) 564 callout_schedule(&global_idle_counter, idle_timeout * hz); 565} 566 567static void 568pmf_class_input_deregister(device_t dv) 569{ 570 device_active_deregister(dv, input_activity_handler); 571} 572 573bool 574pmf_class_input_register(device_t dv) 575{ 576 if (!device_active_register(dv, input_activity_handler)) 577 return false; 578 579 device_pmf_class_register(dv, NULL, NULL, NULL, 580 pmf_class_input_deregister); 581 582 return true; 583} 584 585static void 586pmf_class_display_deregister(device_t dv) 587{ 588 struct display_class_softc *sc = device_pmf_class_private(dv); 589 int s; 590 591 s = splsoftclock(); 592 TAILQ_REMOVE(&all_displays, sc, dc_link); 593 if (TAILQ_EMPTY(&all_displays)) 594 callout_stop(&global_idle_counter); 595 splx(s); 596 597 free(sc, M_DEVBUF); 598} 599 600bool 601pmf_class_display_register(device_t dv) 602{ 603 struct display_class_softc *sc; 604 int s; 605 606 sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK); 607 608 s = splsoftclock(); 609 if (TAILQ_EMPTY(&all_displays)) 610 callout_schedule(&global_idle_counter, idle_timeout * hz); 611 612 TAILQ_INSERT_HEAD(&all_displays, sc, dc_link); 613 splx(s); 614 615 device_pmf_class_register(dv, sc, NULL, NULL, 616 pmf_class_display_deregister); 617 618 return true; 619} 620 621void 622pmf_init(void) 623{ 624 int err; 625 626 KASSERT(pmf_event_workqueue == NULL); 627 err = workqueue_create(&pmf_event_workqueue, "pmfevent", 628 pmf_event_worker, NULL, PRI_NONE, IPL_VM, 0); 629 if (err) 630 panic("couldn't create pmfevent workqueue"); 631 632 callout_init(&global_idle_counter, 0); 633 callout_setfunc(&global_idle_counter, input_idle, NULL); 634} 635