xref: /netbsd-src/sys/dev/hpc/apm/apmdev.c (revision 8b0f9554ff8762542c4defc4f70e1eb76fb508fa)
1 /*	$NetBSD: apmdev.c,v 1.15 2007/12/14 00:51:44 uwe Exp $ */
2 
3 /*-
4  * Copyright (c) 1996, 1997 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by John Kohl and Christopher G. Demetriou.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. All advertising materials mentioning features or use of this software
19  *    must display the following acknowledgement:
20  *        This product includes software developed by the NetBSD
21  *        Foundation, Inc. and its contributors.
22  * 4. Neither the name of The NetBSD Foundation nor the names of its
23  *    contributors may be used to endorse or promote products derived
24  *    from this software without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36  * POSSIBILITY OF SUCH DAMAGE.
37  */
38 /*
39  * from: sys/arch/i386/i386/apm.c,v 1.49 2000/05/08
40  */
41 
42 #include <sys/cdefs.h>
43 __KERNEL_RCSID(0, "$NetBSD: apmdev.c,v 1.15 2007/12/14 00:51:44 uwe Exp $");
44 
45 #ifdef _KERNEL_OPT
46 #include "opt_apmdev.h"
47 #endif
48 
49 #ifdef APM_NOIDLE
50 #error APM_NOIDLE option deprecated; use APM_NO_IDLE instead
51 #endif
52 
53 #if defined(DEBUG) && !defined(APMDEBUG)
54 #define	APMDEBUG
55 #endif
56 
57 #include <sys/param.h>
58 #include <sys/systm.h>
59 #include <sys/signalvar.h>
60 #include <sys/kernel.h>
61 #include <sys/proc.h>
62 #include <sys/kthread.h>
63 #include <sys/mutex.h>
64 #include <sys/user.h>
65 #include <sys/malloc.h>
66 #include <sys/device.h>
67 #include <sys/fcntl.h>
68 #include <sys/ioctl.h>
69 #include <sys/select.h>
70 #include <sys/poll.h>
71 #include <sys/conf.h>
72 
73 #include <dev/hpc/apm/apmvar.h>
74 
75 #include <machine/stdarg.h>
76 
77 #if defined(APMDEBUG)
78 #define	DPRINTF(f, x)		do { if (apmdebug & (f)) printf x; } while (0)
79 
80 #define	APMDEBUG_INFO		0x01
81 #define	APMDEBUG_APMCALLS	0x02
82 #define	APMDEBUG_EVENTS		0x04
83 #define	APMDEBUG_PROBE		0x10
84 #define	APMDEBUG_ATTACH		0x40
85 #define	APMDEBUG_DEVICE		0x20
86 #define	APMDEBUG_ANOM		0x40
87 
88 #ifdef APMDEBUG_VALUE
89 int	apmdebug = APMDEBUG_VALUE;
90 #else
91 int	apmdebug = 0;
92 #endif
93 #else
94 #define	DPRINTF(f, x)		/**/
95 #endif
96 
97 #define APM_NEVENTS 16
98 
99 struct apm_softc {
100 	struct device sc_dev;
101 	struct selinfo sc_rsel;
102 	struct selinfo sc_xsel;
103 	int	sc_flags;
104 	int	event_count;
105 	int	event_ptr;
106 	int	sc_power_state;
107 	lwp_t	*sc_thread;
108 	kmutex_t sc_mutex;
109 	struct apm_event_info event_list[APM_NEVENTS];
110 	struct apm_accessops *ops;
111 	void *cookie;
112 };
113 #define	SCFLAG_OREAD	0x0000001
114 #define	SCFLAG_OWRITE	0x0000002
115 #define	SCFLAG_OPEN	(SCFLAG_OREAD|SCFLAG_OWRITE)
116 
117 #define	APMUNIT(dev)	(minor(dev)&0xf0)
118 #define	APMDEV(dev)	(minor(dev)&0x0f)
119 #define APMDEV_NORMAL	0
120 #define APMDEV_CTL	8
121 
122 /*
123  * A brief note on the locking protocol: it's very simple; we
124  * assert an exclusive lock any time thread context enters the
125  * APM module.  This is both the APM thread itself, as well as
126  * user context.
127  */
128 #define	APM_LOCK(apmsc)		mutex_enter(&(apmsc)->sc_mutex)
129 #define	APM_UNLOCK(apmsc)	mutex_exit(&(apmsc)->sc_mutex)
130 
131 static void	apmattach(struct device *, struct device *, void *);
132 static int	apmmatch(struct device *, struct cfdata *, void *);
133 
134 static void	apm_event_handle(struct apm_softc *, u_int, u_int);
135 static void	apm_periodic_check(struct apm_softc *);
136 static void	apm_thread(void *);
137 static void	apm_perror(const char *, int, ...)
138 		    __attribute__((__format__(__printf__,1,3)));
139 #ifdef APM_POWER_PRINT
140 static void	apm_power_print(struct apm_softc *, struct apm_power_info *);
141 #endif
142 static int	apm_record_event(struct apm_softc *, u_int);
143 static void	apm_set_ver(struct apm_softc *, u_long);
144 static void	apm_standby(struct apm_softc *);
145 static const char *apm_strerror(int);
146 static void	apm_suspend(struct apm_softc *);
147 static void	apm_resume(struct apm_softc *, u_int, u_int);
148 
149 CFATTACH_DECL(apmdev, sizeof(struct apm_softc),
150     apmmatch, apmattach, NULL, NULL);
151 
152 extern struct cfdriver apmdev_cd;
153 
154 dev_type_open(apmdevopen);
155 dev_type_close(apmdevclose);
156 dev_type_ioctl(apmdevioctl);
157 dev_type_poll(apmdevpoll);
158 dev_type_kqfilter(apmdevkqfilter);
159 
160 const struct cdevsw apmdev_cdevsw = {
161 	apmdevopen, apmdevclose, noread, nowrite, apmdevioctl,
162 	nostop, notty, apmdevpoll, nommap, apmdevkqfilter, D_OTHER
163 };
164 
165 /* configurable variables */
166 int	apm_bogus_bios = 0;
167 #ifdef APM_DISABLE
168 int	apm_enabled = 0;
169 #else
170 int	apm_enabled = 1;
171 #endif
172 #ifdef APM_NO_IDLE
173 int	apm_do_idle = 0;
174 #else
175 int	apm_do_idle = 1;
176 #endif
177 #ifdef APM_NO_STANDBY
178 int	apm_do_standby = 0;
179 #else
180 int	apm_do_standby = 1;
181 #endif
182 #ifdef APM_V10_ONLY
183 int	apm_v11_enabled = 0;
184 #else
185 int	apm_v11_enabled = 1;
186 #endif
187 #ifdef APM_NO_V12
188 int	apm_v12_enabled = 0;
189 #else
190 int	apm_v12_enabled = 1;
191 #endif
192 
193 /* variables used during operation (XXX cgd) */
194 u_char	apm_majver, apm_minver;
195 int	apm_inited;
196 int	apm_standbys, apm_userstandbys, apm_suspends, apm_battlow;
197 int	apm_damn_fool_bios, apm_op_inprog;
198 int	apm_evindex;
199 
200 static int apm_spl;		/* saved spl while suspended */
201 
202 static const char *
203 apm_strerror(int code)
204 {
205 	switch (code) {
206 	case APM_ERR_PM_DISABLED:
207 		return ("power management disabled");
208 	case APM_ERR_REALALREADY:
209 		return ("real mode interface already connected");
210 	case APM_ERR_NOTCONN:
211 		return ("interface not connected");
212 	case APM_ERR_16ALREADY:
213 		return ("16-bit interface already connected");
214 	case APM_ERR_16NOTSUPP:
215 		return ("16-bit interface not supported");
216 	case APM_ERR_32ALREADY:
217 		return ("32-bit interface already connected");
218 	case APM_ERR_32NOTSUPP:
219 		return ("32-bit interface not supported");
220 	case APM_ERR_UNRECOG_DEV:
221 		return ("unrecognized device ID");
222 	case APM_ERR_ERANGE:
223 		return ("parameter out of range");
224 	case APM_ERR_NOTENGAGED:
225 		return ("interface not engaged");
226 	case APM_ERR_UNABLE:
227 		return ("unable to enter requested state");
228 	case APM_ERR_NOEVENTS:
229 		return ("no pending events");
230 	case APM_ERR_NOT_PRESENT:
231 		return ("no APM present");
232 	default:
233 		return ("unknown error code");
234 	}
235 }
236 
237 static void
238 apm_perror(const char *str, int errinfo, ...) /* XXX cgd */
239 {
240 	va_list ap;
241 
242 	printf("APM ");
243 
244 	va_start(ap, errinfo);
245 	vprintf(str, ap);			/* XXX cgd */
246 	va_end(ap);
247 
248 	printf(": %s\n", apm_strerror(errinfo));
249 }
250 
251 #ifdef APM_POWER_PRINT
252 static void
253 apm_power_print(struct apm_softc *sc, struct apm_power_info *pi)
254 {
255 
256 	if (pi->battery_life != APM_BATT_LIFE_UNKNOWN) {
257 		printf("%s: battery life expectancy: %d%%\n",
258 		    sc->sc_dev.dv_xname, pi->battery_life);
259 	}
260 	printf("%s: A/C state: ", sc->sc_dev.dv_xname);
261 	switch (pi->ac_state) {
262 	case APM_AC_OFF:
263 		printf("off\n");
264 		break;
265 	case APM_AC_ON:
266 		printf("on\n");
267 		break;
268 	case APM_AC_BACKUP:
269 		printf("backup power\n");
270 		break;
271 	default:
272 	case APM_AC_UNKNOWN:
273 		printf("unknown\n");
274 		break;
275 	}
276 	if (apm_major == 1 && apm_minor == 0) {
277 		printf("%s: battery charge state:", sc->sc_dev.dv_xname);
278 		switch (pi->battery_state) {
279 		case APM_BATT_HIGH:
280 			printf("high\n");
281 			break;
282 		case APM_BATT_LOW:
283 			printf("low\n");
284 			break;
285 		case APM_BATT_CRITICAL:
286 			printf("critical\n");
287 			break;
288 		case APM_BATT_CHARGING:
289 			printf("charging\n");
290 			break;
291 		case APM_BATT_UNKNOWN:
292 			printf("unknown\n");
293 			break;
294 		default:
295 			printf("undecoded state %x\n", pi->battery_state);
296 			break;
297 		}
298 	} else {
299 		if (pi->battery_state&APM_BATT_FLAG_CHARGING)
300 			printf("charging ");
301 		}
302 		if (pi->battery_state&APM_BATT_FLAG_UNKNOWN)
303 			printf("unknown\n");
304 		else if (pi->battery_state&APM_BATT_FLAG_CRITICAL)
305 			printf("critical\n");
306 		else if (pi->battery_state&APM_BATT_FLAG_LOW)
307 			printf("low\n");
308 		else if (pi->battery_state&APM_BATT_FLAG_HIGH)
309 			printf("high\n");
310 	}
311 	if (pi->minutes_left != 0) {
312 		printf("%s: estimated ", sc->sc_dev.dv_xname);
313 		printf("%dh ", pi->minutes_left / 60);
314 	}
315 	return;
316 }
317 #endif
318 
319 static void
320 apm_suspend(struct apm_softc *sc)
321 {
322 
323 	if (sc->sc_power_state == PWR_SUSPEND) {
324 #ifdef APMDEBUG
325 		printf("%s: apm_suspend: already suspended?\n",
326 		    sc->sc_dev.dv_xname);
327 #endif
328 		return;
329 	}
330 	sc->sc_power_state = PWR_SUSPEND;
331 
332 	dopowerhooks(PWR_SOFTSUSPEND);
333 	(void) tsleep(sc, PWAIT, "apmsuspend",  hz/2);
334 
335 	apm_spl = splhigh();
336 
337 	dopowerhooks(PWR_SUSPEND);
338 
339 	/* XXX cgd */
340 	(void)sc->ops->set_powstate(sc->cookie, APM_DEV_ALLDEVS, APM_SYS_SUSPEND);
341 }
342 
343 static void
344 apm_standby(struct apm_softc *sc)
345 {
346 
347 	if (sc->sc_power_state == PWR_STANDBY) {
348 #ifdef APMDEBUG
349 		printf("%s: apm_standby: already standing by?\n",
350 		    sc->sc_dev.dv_xname);
351 #endif
352 		return;
353 	}
354 	sc->sc_power_state = PWR_STANDBY;
355 
356 	dopowerhooks(PWR_SOFTSTANDBY);
357 	(void) tsleep(sc, PWAIT, "apmstandby",  hz/2);
358 
359 	apm_spl = splhigh();
360 
361 	dopowerhooks(PWR_STANDBY);
362 	/* XXX cgd */
363 	(void)sc->ops->set_powstate(sc->cookie, APM_DEV_ALLDEVS, APM_SYS_STANDBY);
364 }
365 
366 static void
367 apm_resume(struct apm_softc *sc, u_int event_type, u_int event_info)
368 {
369 
370 	if (sc->sc_power_state == PWR_RESUME) {
371 #ifdef APMDEBUG
372 		printf("%s: apm_resume: already running?\n",
373 		    sc->sc_dev.dv_xname);
374 #endif
375 		return;
376 	}
377 	sc->sc_power_state = PWR_RESUME;
378 
379 	/*
380 	 * Some system requires its clock to be initialized after hybernation.
381 	 */
382 /* XXX
383 	initrtclock();
384 */
385 
386 	inittodr(time_second);
387 	dopowerhooks(PWR_RESUME);
388 
389 	splx(apm_spl);
390 
391 	dopowerhooks(PWR_SOFTRESUME);
392 
393 	apm_record_event(sc, event_type);
394 }
395 
396 /*
397  * return 0 if the user will notice and handle the event,
398  * return 1 if the kernel driver should do so.
399  */
400 static int
401 apm_record_event(struct apm_softc *sc, u_int event_type)
402 {
403 	struct apm_event_info *evp;
404 
405 	if ((sc->sc_flags & SCFLAG_OPEN) == 0)
406 		return 1;		/* no user waiting */
407 	if (sc->event_count == APM_NEVENTS)
408 		return 1;			/* overflow */
409 	evp = &sc->event_list[sc->event_ptr];
410 	sc->event_count++;
411 	sc->event_ptr++;
412 	sc->event_ptr %= APM_NEVENTS;
413 	evp->type = event_type;
414 	evp->index = ++apm_evindex;
415 	selnotify(&sc->sc_rsel, 0);
416 	return (sc->sc_flags & SCFLAG_OWRITE) ? 0 : 1; /* user may handle */
417 }
418 
419 static void
420 apm_event_handle(struct apm_softc *sc, u_int event_code, u_int event_info)
421 {
422 	int error;
423 	const char *code;
424 	struct apm_power_info pi;
425 
426 	switch (event_code) {
427 	case APM_USER_STANDBY_REQ:
428 		DPRINTF(APMDEBUG_EVENTS, ("apmev: user standby request\n"));
429 		if (apm_do_standby) {
430 			if (apm_record_event(sc, event_code))
431 				apm_userstandbys++;
432 			apm_op_inprog++;
433 			(void)sc->ops->set_powstate(sc->cookie,
434 						    APM_DEV_ALLDEVS,
435 						    APM_LASTREQ_INPROG);
436 		} else {
437 			(void)sc->ops->set_powstate(sc->cookie,
438 						    APM_DEV_ALLDEVS,
439 						    APM_LASTREQ_REJECTED);
440 			/* in case BIOS hates being spurned */
441 			sc->ops->enable(sc->cookie, 1);
442 		}
443 		break;
444 
445 	case APM_STANDBY_REQ:
446 		DPRINTF(APMDEBUG_EVENTS, ("apmev: system standby request\n"));
447 		if (apm_standbys || apm_suspends) {
448 			DPRINTF(APMDEBUG_EVENTS | APMDEBUG_ANOM,
449 			    ("damn fool BIOS did not wait for answer\n"));
450 			/* just give up the fight */
451 			apm_damn_fool_bios = 1;
452 		}
453 		if (apm_do_standby) {
454 			if (apm_record_event(sc, event_code))
455 				apm_standbys++;
456 			apm_op_inprog++;
457 			(void)sc->ops->set_powstate(sc->cookie,
458 						    APM_DEV_ALLDEVS,
459 						    APM_LASTREQ_INPROG);
460 		} else {
461 			(void)sc->ops->set_powstate(sc->cookie,
462 						    APM_DEV_ALLDEVS,
463 						    APM_LASTREQ_REJECTED);
464 			/* in case BIOS hates being spurned */
465 			sc->ops->enable(sc->cookie, 1);
466 		}
467 		break;
468 
469 	case APM_USER_SUSPEND_REQ:
470 		DPRINTF(APMDEBUG_EVENTS, ("apmev: user suspend request\n"));
471 		if (apm_record_event(sc, event_code))
472 			apm_suspends++;
473 		apm_op_inprog++;
474 		(void)sc->ops->set_powstate(sc->cookie,
475 					    APM_DEV_ALLDEVS,
476 					    APM_LASTREQ_INPROG);
477 		break;
478 
479 	case APM_SUSPEND_REQ:
480 		DPRINTF(APMDEBUG_EVENTS, ("apmev: system suspend request\n"));
481 		if (apm_standbys || apm_suspends) {
482 			DPRINTF(APMDEBUG_EVENTS | APMDEBUG_ANOM,
483 			    ("damn fool BIOS did not wait for answer\n"));
484 			/* just give up the fight */
485 			apm_damn_fool_bios = 1;
486 		}
487 		if (apm_record_event(sc, event_code))
488 			apm_suspends++;
489 		apm_op_inprog++;
490 		(void)sc->ops->set_powstate(sc->cookie,
491 					    APM_DEV_ALLDEVS,
492 					    APM_LASTREQ_INPROG);
493 		break;
494 
495 	case APM_POWER_CHANGE:
496 		DPRINTF(APMDEBUG_EVENTS, ("apmev: power status change\n"));
497 		error = sc->ops->get_powstat(sc->cookie, &pi);
498 #ifdef APM_POWER_PRINT
499 		/* only print if nobody is catching events. */
500 		if (error == 0 &&
501 		    (sc->sc_flags & (SCFLAG_OREAD|SCFLAG_OWRITE)) == 0)
502 			apm_power_print(sc, &pi);
503 #endif
504 		apm_record_event(sc, event_code);
505 		break;
506 
507 	case APM_NORMAL_RESUME:
508 		DPRINTF(APMDEBUG_EVENTS, ("apmev: resume system\n"));
509 		apm_resume(sc, event_code, event_info);
510 		break;
511 
512 	case APM_CRIT_RESUME:
513 		DPRINTF(APMDEBUG_EVENTS, ("apmev: critical resume system"));
514 		apm_resume(sc, event_code, event_info);
515 		break;
516 
517 	case APM_SYS_STANDBY_RESUME:
518 		DPRINTF(APMDEBUG_EVENTS, ("apmev: system standby resume\n"));
519 		apm_resume(sc, event_code, event_info);
520 		break;
521 
522 	case APM_UPDATE_TIME:
523 		DPRINTF(APMDEBUG_EVENTS, ("apmev: update time\n"));
524 		apm_resume(sc, event_code, event_info);
525 		break;
526 
527 	case APM_CRIT_SUSPEND_REQ:
528 		DPRINTF(APMDEBUG_EVENTS, ("apmev: critical system suspend\n"));
529 		apm_record_event(sc, event_code);
530 		apm_suspend(sc);
531 		break;
532 
533 	case APM_BATTERY_LOW:
534 		DPRINTF(APMDEBUG_EVENTS, ("apmev: battery low\n"));
535 		apm_battlow++;
536 		apm_record_event(sc, event_code);
537 		break;
538 
539 	case APM_CAP_CHANGE:
540 		DPRINTF(APMDEBUG_EVENTS, ("apmev: capability change\n"));
541 		if (apm_minver < 2) {
542 			DPRINTF(APMDEBUG_EVENTS, ("apm: unexpected event\n"));
543 		} else {
544 			u_int numbatts, capflags;
545 			sc->ops->get_capabilities(sc->cookie,
546 						  &numbatts, &capflags);
547 			sc->ops->get_powstat(sc->cookie, &pi); /* XXX */
548 		}
549 		break;
550 
551 	default:
552 		switch (event_code >> 8) {
553 			case 0:
554 				code = "reserved system";
555 				break;
556 			case 1:
557 				code = "reserved device";
558 				break;
559 			case 2:
560 				code = "OEM defined";
561 				break;
562 			default:
563 				code = "reserved";
564 				break;
565 		}
566 		printf("APM: %s event code %x\n", code, event_code);
567 	}
568 }
569 
570 static void
571 apm_periodic_check(struct apm_softc *sc)
572 {
573 	int error;
574 	u_int event_code, event_info;
575 
576 
577 	/*
578 	 * tell the BIOS we're working on it, if asked to do a
579 	 * suspend/standby
580 	 */
581 	if (apm_op_inprog)
582 		sc->ops->set_powstate(sc->cookie, APM_DEV_ALLDEVS,
583 				      APM_LASTREQ_INPROG);
584 
585 	while ((error = sc->ops->get_event(sc->cookie, &event_code,
586 					   &event_info)) == 0
587 	       && !apm_damn_fool_bios)
588 		apm_event_handle(sc, event_code, event_info);
589 
590 	if (error != APM_ERR_NOEVENTS)
591 		apm_perror("get event", error);
592 	if (apm_suspends) {
593 		apm_op_inprog = 0;
594 		apm_suspend(sc);
595 	} else if (apm_standbys || apm_userstandbys) {
596 		apm_op_inprog = 0;
597 		apm_standby(sc);
598 	}
599 	apm_suspends = apm_standbys = apm_battlow = apm_userstandbys = 0;
600 	apm_damn_fool_bios = 0;
601 }
602 
603 static void
604 apm_set_ver(struct apm_softc *self, u_long detail)
605 {
606 
607 	if (apm_v12_enabled &&
608 	    APM_MAJOR_VERS(detail) == 1 &&
609 	    APM_MINOR_VERS(detail) == 2) {
610 		apm_majver = 1;
611 		apm_minver = 2;
612 		goto ok;
613 	}
614 
615 	if (apm_v11_enabled &&
616 	    APM_MAJOR_VERS(detail) == 1 &&
617 	    APM_MINOR_VERS(detail) == 1) {
618 		apm_majver = 1;
619 		apm_minver = 1;
620 	} else {
621 		apm_majver = 1;
622 		apm_minver = 0;
623 	}
624 ok:
625 	printf("Power Management spec V%d.%d", apm_majver, apm_minver);
626 	apm_inited = 1;
627 	if (detail & APM_IDLE_SLOWS) {
628 #ifdef DIAGNOSTIC
629 		/* not relevant often */
630 		printf(" (slowidle)");
631 #endif
632 		/* leave apm_do_idle at its user-configured setting */
633 	} else
634 		apm_do_idle = 0;
635 #ifdef DIAGNOSTIC
636 	if (detail & APM_BIOS_PM_DISABLED)
637 		printf(" (BIOS mgmt disabled)");
638 	if (detail & APM_BIOS_PM_DISENGAGED)
639 		printf(" (BIOS managing devices)");
640 #endif
641 }
642 
643 static int
644 apmmatch(struct device *parent,
645 	 struct cfdata *match, void *aux)
646 {
647 
648 	/* There can be only one! */
649 	if (apm_inited)
650 		return 0;
651 
652 	return (1);
653 }
654 
655 static void
656 apmattach(struct device *parent, struct device *self, void *aux)
657 {
658 	struct apm_softc *sc = (void *)self;
659 	struct apmdev_attach_args *aaa = aux;
660 	struct apm_power_info pinfo;
661 	u_int numbatts, capflags;
662 	int error;
663 
664 	printf(": ");
665 
666 	sc->ops = aaa->accessops;
667 	sc->cookie = aaa->accesscookie;
668 
669 	switch ((APM_MAJOR_VERS(aaa->apm_detail) << 8) +
670 		APM_MINOR_VERS(aaa->apm_detail)) {
671 	case 0x0100:
672 		apm_v11_enabled = 0;
673 		apm_v12_enabled = 0;
674 		break;
675 	case 0x0101:
676 		apm_v12_enabled = 0;
677 		/* fall through */
678 	case 0x0102:
679 	default:
680 		break;
681 	}
682 
683 	apm_set_ver(sc, aaa->apm_detail);	/* prints version info */
684 	printf("\n");
685 	if (apm_minver >= 2)
686 		sc->ops->get_capabilities(sc->cookie, &numbatts, &capflags);
687 
688 	/*
689 	 * enable power management
690 	 */
691 	sc->ops->enable(sc->cookie, 1);
692 
693 	error = sc->ops->get_powstat(sc->cookie, &pinfo);
694 	if (error == 0) {
695 #ifdef APM_POWER_PRINT
696 		apm_power_print(apmsc, &pinfo);
697 #endif
698 	} else
699 		apm_perror("get power status", error);
700 	sc->ops->cpu_busy(sc->cookie);
701 
702 	mutex_init(&sc->sc_mutex, MUTEX_DEFAULT, IPL_NONE);
703 
704 	/* Initial state is `resumed'. */
705 	sc->sc_power_state = PWR_RESUME;
706 
707 	/* Do an initial check. */
708 	apm_periodic_check(sc);
709 
710 	/*
711 	 * Create a kernel thread to periodically check for APM events,
712 	 * and notify other subsystems when they occur.
713 	 */
714 	if (kthread_create(PRI_NONE, 0, NULL, apm_thread, sc,
715 	    &sc->sc_thread, "%s", sc->sc_dev.dv_xname) != 0) {
716 		/*
717 		 * We were unable to create the APM thread; bail out.
718 		 */
719 		sc->ops->disconnect(sc->cookie);
720 		printf("%s: unable to create thread, "
721 		    "kernel APM support disabled\n",
722 		       sc->sc_dev.dv_xname);
723 	}
724 }
725 
726 /*
727  * Print function (for parent devices).
728  */
729 int
730 apmprint(void *aux, const char *pnp)
731 {
732 	if (pnp)
733 		aprint_normal("apm at %s", pnp);
734 
735 	return (UNCONF);
736 }
737 void
738 apm_thread(void *arg)
739 {
740 	struct apm_softc *apmsc = arg;
741 
742 	/*
743 	 * Loop forever, doing a periodic check for APM events.
744 	 */
745 	for (;;) {
746 		APM_LOCK(apmsc);
747 		apm_periodic_check(apmsc);
748 		APM_UNLOCK(apmsc);
749 		(void) tsleep(apmsc, PWAIT, "apmev",  (8 * hz) / 7);
750 	}
751 }
752 
753 int
754 apmdevopen(dev_t dev, int flag, int mode, struct lwp *l)
755 {
756 	int unit = APMUNIT(dev);
757 	int ctl = APMDEV(dev);
758 	int error = 0;
759 	struct apm_softc *sc;
760 
761 	if (unit >= apmdev_cd.cd_ndevs)
762 		return ENXIO;
763 	sc = apmdev_cd.cd_devs[unit];
764 	if (!sc)
765 		return ENXIO;
766 
767 	if (!apm_inited)
768 		return ENXIO;
769 
770 	DPRINTF(APMDEBUG_DEVICE,
771 	    ("apmopen: pid %d flag %x mode %x\n", l->l_proc->p_pid, flag, mode));
772 
773 	APM_LOCK(sc);
774 	switch (ctl) {
775 	case APMDEV_CTL:
776 		if (!(flag & FWRITE)) {
777 			error = EINVAL;
778 			break;
779 		}
780 		if (sc->sc_flags & SCFLAG_OWRITE) {
781 			error = EBUSY;
782 			break;
783 		}
784 		sc->sc_flags |= SCFLAG_OWRITE;
785 		break;
786 	case APMDEV_NORMAL:
787 		if (!(flag & FREAD) || (flag & FWRITE)) {
788 			error = EINVAL;
789 			break;
790 		}
791 		sc->sc_flags |= SCFLAG_OREAD;
792 		break;
793 	default:
794 		error = ENXIO;
795 		break;
796 	}
797 	APM_UNLOCK(sc);
798 
799 	return (error);
800 }
801 
802 int
803 apmdevclose(dev_t dev, int flag, int mode,
804 	    struct lwp *l)
805 {
806 	struct apm_softc *sc = apmdev_cd.cd_devs[APMUNIT(dev)];
807 	int ctl = APMDEV(dev);
808 
809 	DPRINTF(APMDEBUG_DEVICE,
810 	    ("apmclose: pid %d flag %x mode %x\n", l->l_proc->p_pid, flag, mode));
811 
812 	APM_LOCK(sc);
813 	switch (ctl) {
814 	case APMDEV_CTL:
815 		sc->sc_flags &= ~SCFLAG_OWRITE;
816 		break;
817 	case APMDEV_NORMAL:
818 		sc->sc_flags &= ~SCFLAG_OREAD;
819 		break;
820 	}
821 	if ((sc->sc_flags & SCFLAG_OPEN) == 0) {
822 		sc->event_count = 0;
823 		sc->event_ptr = 0;
824 	}
825 	APM_UNLOCK(sc);
826 	return 0;
827 }
828 
829 int
830 apmdevioctl(dev_t dev, u_long cmd, void *data, int flag,
831 	    struct lwp *l)
832 {
833 	struct apm_softc *sc = apmdev_cd.cd_devs[APMUNIT(dev)];
834 	struct apm_power_info *powerp;
835 	struct apm_event_info *evp;
836 #if 0
837 	struct apm_ctl *actl;
838 #endif
839 	int i, error = 0;
840 	int batt_flags;
841 
842 	APM_LOCK(sc);
843 	switch (cmd) {
844 	case APM_IOC_STANDBY:
845 		if (!apm_do_standby) {
846 			error = EOPNOTSUPP;
847 			break;
848 		}
849 
850 		if ((flag & FWRITE) == 0) {
851 			error = EBADF;
852 			break;
853 		}
854 		apm_userstandbys++;
855 		break;
856 
857 	case APM_IOC_SUSPEND:
858 		if ((flag & FWRITE) == 0) {
859 			error = EBADF;
860 			break;
861 		}
862 		apm_suspends++;
863 		break;
864 
865 	case APM_IOC_NEXTEVENT:
866 		if (!sc->event_count)
867 			error = EAGAIN;
868 		else {
869 			evp = (struct apm_event_info *)data;
870 			i = sc->event_ptr + APM_NEVENTS - sc->event_count;
871 			i %= APM_NEVENTS;
872 			*evp = sc->event_list[i];
873 			sc->event_count--;
874 		}
875 		break;
876 
877 	case OAPM_IOC_GETPOWER:
878 	case APM_IOC_GETPOWER:
879 		powerp = (struct apm_power_info *)data;
880 		if ((error = sc->ops->get_powstat(sc->cookie, powerp)) != 0) {
881 			apm_perror("ioctl get power status", error);
882 			error = EIO;
883 			break;
884 		}
885 		switch (apm_minver) {
886 		case 0:
887 			break;
888 		case 1:
889 		default:
890 			batt_flags = powerp->battery_state;
891 			powerp->battery_state = APM_BATT_UNKNOWN;
892 			if (batt_flags & APM_BATT_FLAG_HIGH)
893 				powerp->battery_state = APM_BATT_HIGH;
894 			else if (batt_flags & APM_BATT_FLAG_LOW)
895 				powerp->battery_state = APM_BATT_LOW;
896 			else if (batt_flags & APM_BATT_FLAG_CRITICAL)
897 				powerp->battery_state = APM_BATT_CRITICAL;
898 			else if (batt_flags & APM_BATT_FLAG_CHARGING)
899 				powerp->battery_state = APM_BATT_CHARGING;
900 			else if (batt_flags & APM_BATT_FLAG_NO_SYSTEM_BATTERY)
901 				powerp->battery_state = APM_BATT_ABSENT;
902 			break;
903 		}
904 		break;
905 
906 	default:
907 		error = ENOTTY;
908 	}
909 	APM_UNLOCK(sc);
910 
911 	return (error);
912 }
913 
914 int
915 apmdevpoll(dev_t dev, int events, struct lwp *l)
916 {
917 	struct apm_softc *sc = apmdev_cd.cd_devs[APMUNIT(dev)];
918 	int revents = 0;
919 
920 	APM_LOCK(sc);
921 	if (events & (POLLIN | POLLRDNORM)) {
922 		if (sc->event_count)
923 			revents |= events & (POLLIN | POLLRDNORM);
924 		else
925 			selrecord(l, &sc->sc_rsel);
926 	}
927 	APM_UNLOCK(sc);
928 
929 	return (revents);
930 }
931 
932 static void
933 filt_apmrdetach(struct knote *kn)
934 {
935 	struct apm_softc *sc = kn->kn_hook;
936 
937 	APM_LOCK(sc);
938 	SLIST_REMOVE(&sc->sc_rsel.sel_klist, kn, knote, kn_selnext);
939 	APM_UNLOCK(sc);
940 }
941 
942 static int
943 filt_apmread(struct knote *kn, long hint)
944 {
945 	struct apm_softc *sc = kn->kn_hook;
946 
947 	kn->kn_data = sc->event_count;
948 	return (kn->kn_data > 0);
949 }
950 
951 static const struct filterops apmread_filtops =
952 	{ 1, NULL, filt_apmrdetach, filt_apmread };
953 
954 int
955 apmdevkqfilter(dev_t dev, struct knote *kn)
956 {
957 	struct apm_softc *sc = apmdev_cd.cd_devs[APMUNIT(dev)];
958 	struct klist *klist;
959 
960 	switch (kn->kn_filter) {
961 	case EVFILT_READ:
962 		klist = &sc->sc_rsel.sel_klist;
963 		kn->kn_fop = &apmread_filtops;
964 		break;
965 
966 	default:
967 		return (EINVAL);
968 	}
969 
970 	kn->kn_hook = sc;
971 
972 	APM_LOCK(sc);
973 	SLIST_INSERT_HEAD(klist, kn, kn_selnext);
974 	APM_UNLOCK(sc);
975 
976 	return (0);
977 }
978