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