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