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