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