apm.c revision 126076
1/*
2 * APM (Advanced Power Management) BIOS Device Driver
3 *
4 * Copyright (c) 1994 UKAI, Fumitoshi.
5 * Copyright (c) 1994-1995 by HOSOKAWA, Tatsumi <hosokawa@jp.FreeBSD.org>
6 * Copyright (c) 1996 Nate Williams <nate@FreeBSD.org>
7 * Copyright (c) 1997 Poul-Henning Kamp <phk@FreeBSD.org>
8 *
9 * This software may be used, modified, copied, and distributed, in
10 * both source and binary form provided that the above copyright and
11 * these terms are retained. Under no circumstances is the author
12 * responsible for the proper functioning of this software, nor does
13 * the author assume any responsibility for damages incurred with its
14 * use.
15 *
16 * Sep, 1994	Implemented on FreeBSD 1.1.5.1R (Toshiba AVS001WD)
17 */
18
19#include <sys/cdefs.h>
20__FBSDID("$FreeBSD: head/sys/i386/bios/apm.c 126076 2004-02-21 19:42:58Z phk $");
21
22#include <sys/param.h>
23#include <sys/systm.h>
24#include <sys/bus.h>
25#include <sys/conf.h>
26#include <sys/eventhandler.h>
27#include <sys/fcntl.h>
28#include <sys/kernel.h>
29#include <sys/poll.h>
30#include <sys/power.h>
31#include <sys/reboot.h>
32#include <sys/selinfo.h>
33#include <sys/signalvar.h>
34#include <sys/sysctl.h>
35#include <sys/syslog.h>
36#include <sys/time.h>
37#include <sys/uio.h>
38
39#include <machine/apm_bios.h>
40#include <machine/clock.h>
41#include <machine/endian.h>
42#include <machine/pc/bios.h>
43#include <machine/cpufunc.h>
44#include <machine/segments.h>
45#include <machine/stdarg.h>
46#include <machine/vm86.h>
47
48#include <machine/bus.h>
49#include <machine/resource.h>
50#include <sys/rman.h>
51
52#include <vm/vm.h>
53#include <vm/pmap.h>
54#include <vm/vm_param.h>
55
56#include <i386/bios/apm.h>
57
58/* Used by the apm_saver screen saver module */
59int apm_display(int newstate);
60struct apm_softc apm_softc;
61
62static void apm_resume(void);
63static int apm_bioscall(void);
64static int apm_check_function_supported(u_int version, u_int func);
65
66static int apm_pm_func(u_long, void*, ...);
67
68static u_long	apm_version;
69
70int	apm_evindex;
71
72#define	SCFLAG_ONORMAL	0x0000001
73#define	SCFLAG_OCTL	0x0000002
74#define	SCFLAG_OPEN	(SCFLAG_ONORMAL|SCFLAG_OCTL)
75
76#define APMDEV(dev)	(minor(dev)&0x0f)
77#define APMDEV_NORMAL	0
78#define APMDEV_CTL	8
79
80#ifdef PC98
81extern int bios32_apm98(struct bios_regs *, u_int, u_short);
82
83/* PC98's SMM definition */
84#define	APM_NECSMM_PORT		0x6b8e
85#define	APM_NECSMM_PORTSZ	1
86#define	APM_NECSMM_EN		0x10
87static __inline void apm_enable_smm(struct apm_softc *);
88static __inline void apm_disable_smm(struct apm_softc *);
89int apm_necsmm_addr;
90u_int32_t apm_necsmm_mask;
91#endif
92
93static struct apmhook	*hook[NAPM_HOOK];		/* XXX */
94
95#define is_enabled(foo) ((foo) ? "enabled" : "disabled")
96
97/* Map version number to integer (keeps ordering of version numbers) */
98#define INTVERSION(major, minor)	((major)*100 + (minor))
99
100static struct callout_handle apm_timeout_ch =
101    CALLOUT_HANDLE_INITIALIZER(&apm_timeout_ch);
102
103static timeout_t apm_timeout;
104static d_open_t apmopen;
105static d_close_t apmclose;
106static d_write_t apmwrite;
107static d_ioctl_t apmioctl;
108static d_poll_t apmpoll;
109
110static struct cdevsw apm_cdevsw = {
111	.d_open =	apmopen,
112	.d_close =	apmclose,
113	.d_write =	apmwrite,
114	.d_ioctl =	apmioctl,
115	.d_poll =	apmpoll,
116	.d_name =	"apm",
117};
118
119static int apm_suspend_delay = 1;
120static int apm_standby_delay = 1;
121static int apm_swab_batt_minutes = 0;
122static int apm_debug = 0;
123
124#define APM_DPRINT(args...) do	{					\
125	if (apm_debug) {						\
126		printf(args);						\
127	}								\
128} while (0)
129
130SYSCTL_INT(_machdep, OID_AUTO, apm_suspend_delay, CTLFLAG_RW, &apm_suspend_delay, 1, "");
131SYSCTL_INT(_machdep, OID_AUTO, apm_standby_delay, CTLFLAG_RW, &apm_standby_delay, 1, "");
132SYSCTL_INT(_debug, OID_AUTO, apm_debug, CTLFLAG_RW, &apm_debug, 0, "");
133
134TUNABLE_INT("machdep.apm_swab_batt_minutes", &apm_swab_batt_minutes);
135SYSCTL_INT(_machdep, OID_AUTO, apm_swab_batt_minutes, CTLFLAG_RW,
136	   &apm_swab_batt_minutes, 0, "Byte swap battery time value.");
137
138#ifdef PC98
139static __inline void
140apm_enable_smm(sc)
141	struct apm_softc *sc;
142{
143	bus_space_tag_t iot = sc->sc_iot;
144	bus_space_handle_t ioh = sc->sc_ioh;
145	if (apm_necsmm_addr != 0)
146		bus_space_write_1(iot, ioh, 0,
147			  (bus_space_read_1(iot, ioh, 0) | ~apm_necsmm_mask));
148}
149
150static __inline void
151apm_disable_smm(sc)
152	struct apm_softc *sc;
153{
154	bus_space_tag_t iot = sc->sc_iot;
155	bus_space_handle_t ioh = sc->sc_ioh;
156	if (apm_necsmm_addr != 0)
157		bus_space_write_1(iot, ioh, 0,
158			  (bus_space_read_1(iot, ioh, 0) & apm_necsmm_mask));
159}
160#endif
161
162/*
163 * return  0 if the function successfull,
164 * return  1 if the function unsuccessfull,
165 * return -1 if the function unsupported.
166 */
167static int
168apm_bioscall(void)
169{
170	struct apm_softc *sc = &apm_softc;
171	int errno = 0;
172	u_int apm_func = sc->bios.r.eax & 0xff;
173
174	if (!apm_check_function_supported(sc->intversion, apm_func)) {
175		APM_DPRINT("apm_bioscall: function 0x%x is not supported in v%d.%d\n",
176		    apm_func, sc->majorversion, sc->minorversion);
177		return (-1);
178	}
179
180	sc->bios_busy = 1;
181#ifdef	PC98
182	set_bios_selectors(&sc->bios.seg, BIOSCODE_FLAG | BIOSDATA_FLAG);
183	if (bios32_apm98(&sc->bios.r, sc->bios.entry,
184	    GSEL(GBIOSCODE32_SEL, SEL_KPL)) != 0)
185		return 1;
186#else
187	if (sc->connectmode == APM_PROT32CONNECT) {
188		set_bios_selectors(&sc->bios.seg,
189				   BIOSCODE_FLAG | BIOSDATA_FLAG);
190		errno = bios32(&sc->bios.r,
191			       sc->bios.entry, GSEL(GBIOSCODE32_SEL, SEL_KPL));
192	} else {
193		errno = bios16(&sc->bios, NULL);
194	}
195#endif
196	sc->bios_busy = 0;
197	return (errno);
198}
199
200/* check whether APM function is supported (1)  or not (0). */
201static int
202apm_check_function_supported(u_int version, u_int func)
203{
204	/* except driver version */
205	if (func == APM_DRVVERSION) {
206		return (1);
207	}
208#ifdef PC98
209	if (func == APM_GETPWSTATUS) {
210		return (1);
211	}
212#endif
213
214	switch (version) {
215	case INTVERSION(1, 0):
216		if (func > APM_GETPMEVENT) {
217			return (0); /* not supported */
218		}
219		break;
220	case INTVERSION(1, 1):
221		if (func > APM_ENGAGEDISENGAGEPM &&
222		    func < APM_OEMFUNC) {
223			return (0); /* not supported */
224		}
225		break;
226	case INTVERSION(1, 2):
227		break;
228	}
229
230	return (1); /* supported */
231}
232
233/* enable/disable power management */
234static int
235apm_enable_disable_pm(int enable)
236{
237	struct apm_softc *sc = &apm_softc;
238
239	sc->bios.r.eax = (APM_BIOS << 8) | APM_ENABLEDISABLEPM;
240
241	if (sc->intversion >= INTVERSION(1, 1))
242		sc->bios.r.ebx  = PMDV_ALLDEV;
243	else
244		sc->bios.r.ebx  = 0xffff;	/* APM version 1.0 only */
245	sc->bios.r.ecx  = enable;
246	sc->bios.r.edx = 0;
247	return (apm_bioscall());
248}
249
250/* register driver version (APM 1.1 or later) */
251static int
252apm_driver_version(int version)
253{
254	struct apm_softc *sc = &apm_softc;
255
256	sc->bios.r.eax = (APM_BIOS << 8) | APM_DRVVERSION;
257	sc->bios.r.ebx  = 0x0;
258	sc->bios.r.ecx  = version;
259	sc->bios.r.edx = 0;
260
261	if (apm_bioscall() == 0 && sc->bios.r.eax == version)
262		return (0);
263
264	/* Some old BIOSes don't return the connection version in %ax. */
265	if (sc->bios.r.eax == ((APM_BIOS << 8) | APM_DRVVERSION))
266		return (0);
267
268	return (1);
269}
270
271/* engage/disengage power management (APM 1.1 or later) */
272static int
273apm_engage_disengage_pm(int engage)
274{
275	struct apm_softc *sc = &apm_softc;
276
277	sc->bios.r.eax = (APM_BIOS << 8) | APM_ENGAGEDISENGAGEPM;
278	sc->bios.r.ebx = PMDV_ALLDEV;
279	sc->bios.r.ecx = engage;
280	sc->bios.r.edx = 0;
281	return (apm_bioscall());
282}
283
284/* get PM event */
285static u_int
286apm_getevent(void)
287{
288	struct apm_softc *sc = &apm_softc;
289
290	sc->bios.r.eax = (APM_BIOS << 8) | APM_GETPMEVENT;
291
292	sc->bios.r.ebx = 0;
293	sc->bios.r.ecx = 0;
294	sc->bios.r.edx = 0;
295	if (apm_bioscall())
296		return (PMEV_NOEVENT);
297	return (sc->bios.r.ebx & 0xffff);
298}
299
300/* suspend entire system */
301static int
302apm_suspend_system(int state)
303{
304	struct apm_softc *sc = &apm_softc;
305
306	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
307	sc->bios.r.ebx = PMDV_ALLDEV;
308	sc->bios.r.ecx = state;
309	sc->bios.r.edx = 0;
310
311#ifdef PC98
312	apm_disable_smm(sc);
313#endif
314	if (apm_bioscall()) {
315 		printf("Entire system suspend failure: errcode = %d\n",
316		       0xff & (sc->bios.r.eax >> 8));
317 		return 1;
318 	}
319#ifdef PC98
320	apm_enable_smm(sc);
321#endif
322 	return 0;
323}
324
325/* Display control */
326/*
327 * Experimental implementation: My laptop machine can't handle this function
328 * If your laptop can control the display via APM, please inform me.
329 *                            HOSOKAWA, Tatsumi <hosokawa@jp.FreeBSD.org>
330 */
331int
332apm_display(int newstate)
333{
334	struct apm_softc *sc = &apm_softc;
335
336	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
337	sc->bios.r.ebx = PMDV_DISP0;
338	sc->bios.r.ecx = newstate ? PMST_APMENABLED:PMST_SUSPEND;
339	sc->bios.r.edx = 0;
340	if (apm_bioscall() == 0) {
341		return 0;
342 	}
343
344	/* If failed, then try to blank all display devices instead. */
345	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
346	sc->bios.r.ebx = PMDV_DISPALL;	/* all display devices */
347	sc->bios.r.ecx = newstate ? PMST_APMENABLED:PMST_SUSPEND;
348	sc->bios.r.edx = 0;
349	if (apm_bioscall() == 0) {
350		return 0;
351 	}
352 	printf("Display off failure: errcode = %d\n",
353	       0xff & (sc->bios.r.eax >> 8));
354 	return 1;
355}
356
357/*
358 * Turn off the entire system.
359 */
360static void
361apm_power_off(void *junk, int howto)
362{
363	struct apm_softc *sc = &apm_softc;
364
365	/* Not halting powering off, or not active */
366	if (!(howto & RB_POWEROFF) || !apm_softc.active)
367		return;
368	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
369	sc->bios.r.ebx = PMDV_ALLDEV;
370	sc->bios.r.ecx = PMST_OFF;
371	sc->bios.r.edx = 0;
372	(void) apm_bioscall();
373}
374
375/* APM Battery low handler */
376static void
377apm_battery_low(void)
378{
379	printf("\007\007 * * * BATTERY IS LOW * * * \007\007");
380}
381
382/* APM hook manager */
383static struct apmhook *
384apm_add_hook(struct apmhook **list, struct apmhook *ah)
385{
386	int s;
387	struct apmhook *p, *prev;
388
389	APM_DPRINT("Add hook \"%s\"\n", ah->ah_name);
390
391	s = splhigh();
392	if (ah == NULL)
393		panic("illegal apm_hook!");
394	prev = NULL;
395	for (p = *list; p != NULL; prev = p, p = p->ah_next)
396		if (p->ah_order > ah->ah_order)
397			break;
398
399	if (prev == NULL) {
400		ah->ah_next = *list;
401		*list = ah;
402	} else {
403		ah->ah_next = prev->ah_next;
404		prev->ah_next = ah;
405	}
406	splx(s);
407	return ah;
408}
409
410static void
411apm_del_hook(struct apmhook **list, struct apmhook *ah)
412{
413	int s;
414	struct apmhook *p, *prev;
415
416	s = splhigh();
417	prev = NULL;
418	for (p = *list; p != NULL; prev = p, p = p->ah_next)
419		if (p == ah)
420			goto deleteit;
421	panic("Tried to delete unregistered apm_hook.");
422	goto nosuchnode;
423deleteit:
424	if (prev != NULL)
425		prev->ah_next = p->ah_next;
426	else
427		*list = p->ah_next;
428nosuchnode:
429	splx(s);
430}
431
432
433/* APM driver calls some functions automatically */
434static void
435apm_execute_hook(struct apmhook *list)
436{
437	struct apmhook *p;
438
439	for (p = list; p != NULL; p = p->ah_next) {
440		APM_DPRINT("Execute APM hook \"%s.\"\n", p->ah_name);
441		if ((*(p->ah_fun))(p->ah_arg))
442			printf("Warning: APM hook \"%s\" failed", p->ah_name);
443	}
444}
445
446
447/* establish an apm hook */
448struct apmhook *
449apm_hook_establish(int apmh, struct apmhook *ah)
450{
451	if (apmh < 0 || apmh >= NAPM_HOOK)
452		return NULL;
453
454	return apm_add_hook(&hook[apmh], ah);
455}
456
457/* disestablish an apm hook */
458void
459apm_hook_disestablish(int apmh, struct apmhook *ah)
460{
461	if (apmh < 0 || apmh >= NAPM_HOOK)
462		return;
463
464	apm_del_hook(&hook[apmh], ah);
465}
466
467static int apm_record_event(struct apm_softc *, u_int);
468static void apm_processevent(void);
469
470static u_int apm_op_inprog = 0;
471
472static void
473apm_do_suspend(void)
474{
475	struct apm_softc *sc = &apm_softc;
476	int error;
477
478	if (!sc)
479		return;
480
481	apm_op_inprog = 0;
482	sc->suspends = sc->suspend_countdown = 0;
483
484	if (sc->initialized) {
485		error = DEVICE_SUSPEND(root_bus);
486		if (error) {
487			DEVICE_RESUME(root_bus);
488		} else {
489			apm_execute_hook(hook[APM_HOOK_SUSPEND]);
490			if (apm_suspend_system(PMST_SUSPEND) == 0) {
491				sc->suspending = 1;
492				apm_processevent();
493			} else {
494				/* Failure, 'resume' the system again */
495				apm_execute_hook(hook[APM_HOOK_RESUME]);
496				DEVICE_RESUME(root_bus);
497			}
498		}
499	}
500}
501
502static void
503apm_do_standby(void)
504{
505	struct apm_softc *sc = &apm_softc;
506
507	if (!sc)
508		return;
509
510	apm_op_inprog = 0;
511	sc->standbys = sc->standby_countdown = 0;
512
513	if (sc->initialized) {
514		/*
515		 * As far as standby, we don't need to execute
516		 * all of suspend hooks.
517		 */
518		if (apm_suspend_system(PMST_STANDBY) == 0)
519			apm_processevent();
520	}
521}
522
523static void
524apm_lastreq_notify(void)
525{
526	struct apm_softc *sc = &apm_softc;
527
528	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
529	sc->bios.r.ebx = PMDV_ALLDEV;
530	sc->bios.r.ecx = PMST_LASTREQNOTIFY;
531	sc->bios.r.edx = 0;
532	apm_bioscall();
533}
534
535static int
536apm_lastreq_rejected(void)
537{
538	struct apm_softc *sc = &apm_softc;
539
540	if (apm_op_inprog == 0) {
541		return 1;	/* no operation in progress */
542	}
543
544	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
545	sc->bios.r.ebx = PMDV_ALLDEV;
546	sc->bios.r.ecx = PMST_LASTREQREJECT;
547	sc->bios.r.edx = 0;
548
549	if (apm_bioscall()) {
550		APM_DPRINT("apm_lastreq_rejected: failed\n");
551		return 1;
552	}
553	apm_op_inprog = 0;
554	return 0;
555}
556
557/*
558 * Public interface to the suspend/resume:
559 *
560 * Execute suspend and resume hook before and after sleep, respectively.
561 *
562 */
563
564void
565apm_suspend(int state)
566{
567	struct apm_softc *sc = &apm_softc;
568
569	if (!sc->initialized)
570		return;
571
572	switch (state) {
573	case PMST_SUSPEND:
574		if (sc->suspends)
575			return;
576		sc->suspends++;
577		sc->suspend_countdown = apm_suspend_delay;
578		break;
579	case PMST_STANDBY:
580		if (sc->standbys)
581			return;
582		sc->standbys++;
583		sc->standby_countdown = apm_standby_delay;
584		break;
585	default:
586		printf("apm_suspend: Unknown Suspend state 0x%x\n", state);
587		return;
588	}
589
590	apm_op_inprog++;
591	apm_lastreq_notify();
592}
593
594static void
595apm_resume(void)
596{
597	struct apm_softc *sc = &apm_softc;
598
599	if (!sc)
600		return;
601
602	if (sc->suspending == 0)
603		return;
604
605	sc->suspending = 0;
606	if (sc->initialized) {
607		apm_execute_hook(hook[APM_HOOK_RESUME]);
608		DEVICE_RESUME(root_bus);
609	}
610}
611
612
613/* get power status per battery */
614static int
615apm_get_pwstatus(apm_pwstatus_t app)
616{
617	struct apm_softc *sc = &apm_softc;
618
619	if (app->ap_device != PMDV_ALLDEV &&
620	    (app->ap_device < PMDV_BATT0 || app->ap_device > PMDV_BATT_ALL))
621		return 1;
622
623	sc->bios.r.eax = (APM_BIOS << 8) | APM_GETPWSTATUS;
624	sc->bios.r.ebx = app->ap_device;
625	sc->bios.r.ecx = 0;
626	sc->bios.r.edx = 0xffff;	/* default to unknown battery time */
627
628	if (apm_bioscall())
629		return 1;
630
631	app->ap_acline    = (sc->bios.r.ebx >> 8) & 0xff;
632	app->ap_batt_stat = sc->bios.r.ebx & 0xff;
633	app->ap_batt_flag = (sc->bios.r.ecx >> 8) & 0xff;
634	app->ap_batt_life = sc->bios.r.ecx & 0xff;
635	sc->bios.r.edx &= 0xffff;
636	if (apm_swab_batt_minutes)
637		sc->bios.r.edx = __bswap16(sc->bios.r.edx) | 0x8000;
638	if (sc->bios.r.edx == 0xffff)	/* Time is unknown */
639		app->ap_batt_time = -1;
640	else if (sc->bios.r.edx & 0x8000)	/* Time is in minutes */
641		app->ap_batt_time = (sc->bios.r.edx & 0x7fff) * 60;
642	else				/* Time is in seconds */
643		app->ap_batt_time = sc->bios.r.edx;
644
645	return 0;
646}
647
648
649/* get APM information */
650static int
651apm_get_info(apm_info_t aip)
652{
653	struct apm_softc *sc = &apm_softc;
654	struct apm_pwstatus aps;
655
656	bzero(&aps, sizeof(aps));
657	aps.ap_device = PMDV_ALLDEV;
658	if (apm_get_pwstatus(&aps))
659		return 1;
660
661	aip->ai_infoversion = 1;
662	aip->ai_acline      = aps.ap_acline;
663	aip->ai_batt_stat   = aps.ap_batt_stat;
664	aip->ai_batt_life   = aps.ap_batt_life;
665	aip->ai_batt_time   = aps.ap_batt_time;
666	aip->ai_major       = (u_int)sc->majorversion;
667	aip->ai_minor       = (u_int)sc->minorversion;
668	aip->ai_status      = (u_int)sc->active;
669
670	sc->bios.r.eax = (APM_BIOS << 8) | APM_GETCAPABILITIES;
671	sc->bios.r.ebx = 0;
672	sc->bios.r.ecx = 0;
673	sc->bios.r.edx = 0;
674	if (apm_bioscall()) {
675		aip->ai_batteries = -1;	/* Unknown */
676		aip->ai_capabilities = 0xff00; /* Unknown, with no bits set */
677	} else {
678		aip->ai_batteries = sc->bios.r.ebx & 0xff;
679		aip->ai_capabilities = sc->bios.r.ecx & 0xf;
680	}
681
682	bzero(aip->ai_spare, sizeof aip->ai_spare);
683
684	return 0;
685}
686
687
688/* inform APM BIOS that CPU is idle */
689void
690apm_cpu_idle(void)
691{
692	struct apm_softc *sc = &apm_softc;
693
694	if (sc->active) {
695
696		sc->bios.r.eax = (APM_BIOS <<8) | APM_CPUIDLE;
697		sc->bios.r.edx = sc->bios.r.ecx = sc->bios.r.ebx = 0;
698		(void) apm_bioscall();
699	}
700	/*
701	 * Some APM implementation halts CPU in BIOS, whenever
702	 * "CPU-idle" function are invoked, but swtch() of
703	 * FreeBSD halts CPU, therefore, CPU is halted twice
704	 * in the sched loop. It makes the interrupt latency
705	 * terribly long and be able to cause a serious problem
706	 * in interrupt processing. We prevent it by removing
707	 * "hlt" operation from swtch() and managed it under
708	 * APM driver.
709	 */
710	if (!sc->active || sc->always_halt_cpu)
711		halt();	/* wait for interrupt */
712}
713
714/* inform APM BIOS that CPU is busy */
715void
716apm_cpu_busy(void)
717{
718	struct apm_softc *sc = &apm_softc;
719
720	/*
721	 * The APM specification says this is only necessary if your BIOS
722	 * slows down the processor in the idle task, otherwise it's not
723	 * necessary.
724	 */
725	if (sc->slow_idle_cpu && sc->active) {
726
727		sc->bios.r.eax = (APM_BIOS <<8) | APM_CPUBUSY;
728		sc->bios.r.edx = sc->bios.r.ecx = sc->bios.r.ebx = 0;
729		apm_bioscall();
730	}
731}
732
733
734/*
735 * APM timeout routine:
736 *
737 * This routine is automatically called by timer once per second.
738 */
739
740static void
741apm_timeout(void *dummy)
742{
743	struct apm_softc *sc = &apm_softc;
744
745	if (apm_op_inprog)
746		apm_lastreq_notify();
747
748	if (sc->standbys && sc->standby_countdown-- <= 0)
749		apm_do_standby();
750
751	if (sc->suspends && sc->suspend_countdown-- <= 0)
752		apm_do_suspend();
753
754	if (!sc->bios_busy)
755		apm_processevent();
756
757	if (sc->active == 1)
758		/* Run slightly more oftan than 1 Hz */
759		apm_timeout_ch = timeout(apm_timeout, NULL, hz - 1);
760}
761
762/* enable APM BIOS */
763static void
764apm_event_enable(void)
765{
766	struct apm_softc *sc = &apm_softc;
767
768	APM_DPRINT("called apm_event_enable()\n");
769	if (sc->initialized) {
770		sc->active = 1;
771		apm_timeout(sc);
772	}
773}
774
775/* disable APM BIOS */
776static void
777apm_event_disable(void)
778{
779	struct apm_softc *sc = &apm_softc;
780
781	APM_DPRINT("called apm_event_disable()\n");
782	if (sc->initialized) {
783		untimeout(apm_timeout, NULL, apm_timeout_ch);
784		sc->active = 0;
785	}
786}
787
788/* halt CPU in scheduling loop */
789static void
790apm_halt_cpu(void)
791{
792	struct apm_softc *sc = &apm_softc;
793
794	if (sc->initialized)
795		sc->always_halt_cpu = 1;
796}
797
798/* don't halt CPU in scheduling loop */
799static void
800apm_not_halt_cpu(void)
801{
802	struct apm_softc *sc = &apm_softc;
803
804	if (sc->initialized)
805		sc->always_halt_cpu = 0;
806}
807
808/* device driver definitions */
809
810/*
811 * Module event
812 */
813
814static int
815apm_modevent(struct module *mod, int event, void *junk)
816{
817
818	switch (event) {
819	case MOD_LOAD:
820		if (!cold)
821			return (EPERM);
822		break;
823	case MOD_UNLOAD:
824		if (!cold && power_pm_get_type() == POWER_PM_TYPE_APM)
825			return (EBUSY);
826		break;
827	default:
828		break;
829	}
830
831	return (0);
832}
833
834/*
835 * Create "connection point"
836 */
837static void
838apm_identify(driver_t *driver, device_t parent)
839{
840	device_t child;
841
842	if (!cold) {
843		printf("Don't load this driver from userland!!\n");
844		return;
845	}
846
847	if (resource_disabled("apm", 0))
848		return;
849
850	child = BUS_ADD_CHILD(parent, 0, "apm", 0);
851	if (child == NULL)
852		panic("apm_identify");
853}
854
855/*
856 * probe for APM BIOS
857 */
858static int
859apm_probe(device_t dev)
860{
861#define APM_KERNBASE	KERNBASE
862	struct vm86frame	vmf;
863	struct apm_softc	*sc = &apm_softc;
864	int			flags;
865#ifdef PC98
866	int			rid;
867#endif
868
869	device_set_desc(dev, "APM BIOS");
870	if (device_get_unit(dev) > 0) {
871		printf("apm: Only one APM driver supported.\n");
872		return ENXIO;
873	}
874
875	if (power_pm_get_type() != POWER_PM_TYPE_NONE &&
876	    power_pm_get_type() != POWER_PM_TYPE_APM) {
877		printf("apm: Other PM system enabled.\n");
878		return ENXIO;
879	}
880
881	if (resource_int_value("apm", 0, "flags", &flags) != 0)
882		flags = 0;
883
884	bzero(&vmf, sizeof(struct vm86frame));		/* safety */
885	bzero(&apm_softc, sizeof(apm_softc));
886	vmf.vmf_ah = APM_BIOS;
887	vmf.vmf_al = APM_INSTCHECK;
888	vmf.vmf_bx = 0;
889	if (vm86_intcall(APM_INT, &vmf))
890		return ENXIO;			/* APM not found */
891	if (vmf.vmf_bx != 0x504d) {
892		printf("apm: incorrect signature (0x%x)\n", vmf.vmf_bx);
893		return ENXIO;
894	}
895	if ((vmf.vmf_cx & (APM_32BIT_SUPPORT | APM_16BIT_SUPPORT)) == 0) {
896		printf("apm: protected mode connections are not supported\n");
897		return ENXIO;
898	}
899
900	apm_version = vmf.vmf_ax;
901	sc->slow_idle_cpu = ((vmf.vmf_cx & APM_CPUIDLE_SLOW) != 0);
902	sc->disabled = ((vmf.vmf_cx & APM_DISABLED) != 0);
903	sc->disengaged = ((vmf.vmf_cx & APM_DISENGAGED) != 0);
904
905	vmf.vmf_ah = APM_BIOS;
906	vmf.vmf_al = APM_DISCONNECT;
907	vmf.vmf_bx = 0;
908        vm86_intcall(APM_INT, &vmf);		/* disconnect, just in case */
909
910#ifdef PC98
911	/* PC98 have bogos APM 32bit BIOS */
912	if ((vmf.vmf_cx & APM_32BIT_SUPPORT) == 0)
913		return ENXIO;
914	rid = 0;
915	bus_set_resource(dev, SYS_RES_IOPORT, rid,
916			 APM_NECSMM_PORT, APM_NECSMM_PORTSZ);
917	sc->sc_res = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid,
918			 APM_NECSMM_PORT, ~0, APM_NECSMM_PORTSZ, RF_ACTIVE);
919	if (sc->sc_res == NULL) {
920		printf("apm: cannot open NEC smm device\n");
921		return ENXIO;
922	}
923	bus_release_resource(dev, SYS_RES_IOPORT, rid, sc->sc_res);
924
925	vmf.vmf_ah = APM_BIOS;
926	vmf.vmf_al = APM_PROT32CONNECT;
927	vmf.vmf_bx = 0;
928	if (vm86_intcall(APM_INT, &vmf)) {
929		printf("apm: 32-bit connection error.\n");
930		return (ENXIO);
931 	}
932
933	sc->bios.seg.code32.base = (vmf.vmf_ax << 4) + APM_KERNBASE;
934	sc->bios.seg.code32.limit = 0xffff;
935	sc->bios.seg.code16.base = (vmf.vmf_cx << 4) + APM_KERNBASE;
936	sc->bios.seg.code16.limit = 0xffff;
937	sc->bios.seg.data.base = (vmf.vmf_dx << 4) + APM_KERNBASE;
938	sc->bios.seg.data.limit = 0xffff;
939	sc->bios.entry = vmf.vmf_ebx;
940	sc->connectmode = APM_PROT32CONNECT;
941#else
942	if ((vmf.vmf_cx & APM_32BIT_SUPPORT) != 0) {
943		vmf.vmf_ah = APM_BIOS;
944		vmf.vmf_al = APM_PROT32CONNECT;
945		vmf.vmf_bx = 0;
946		if (vm86_intcall(APM_INT, &vmf)) {
947			printf("apm: 32-bit connection error.\n");
948			return (ENXIO);
949 		}
950		sc->bios.seg.code32.base = (vmf.vmf_ax << 4) + APM_KERNBASE;
951		sc->bios.seg.code32.limit = 0xffff;
952		sc->bios.seg.code16.base = (vmf.vmf_cx << 4) + APM_KERNBASE;
953		sc->bios.seg.code16.limit = 0xffff;
954		sc->bios.seg.data.base = (vmf.vmf_dx << 4) + APM_KERNBASE;
955		sc->bios.seg.data.limit = 0xffff;
956		sc->bios.entry = vmf.vmf_ebx;
957		sc->connectmode = APM_PROT32CONNECT;
958 	} else {
959		/* use 16-bit connection */
960		vmf.vmf_ah = APM_BIOS;
961		vmf.vmf_al = APM_PROT16CONNECT;
962		vmf.vmf_bx = 0;
963		if (vm86_intcall(APM_INT, &vmf)) {
964			printf("apm: 16-bit connection error.\n");
965			return (ENXIO);
966		}
967		sc->bios.seg.code16.base = (vmf.vmf_ax << 4) + APM_KERNBASE;
968		sc->bios.seg.code16.limit = 0xffff;
969		sc->bios.seg.data.base = (vmf.vmf_cx << 4) + APM_KERNBASE;
970		sc->bios.seg.data.limit = 0xffff;
971		sc->bios.entry = vmf.vmf_bx;
972		sc->connectmode = APM_PROT16CONNECT;
973	}
974#endif
975	return(0);
976}
977
978
979/*
980 * return 0 if the user will notice and handle the event,
981 * return 1 if the kernel driver should do so.
982 */
983static int
984apm_record_event(struct apm_softc *sc, u_int event_type)
985{
986	struct apm_event_info *evp;
987
988	if ((sc->sc_flags & SCFLAG_OPEN) == 0)
989		return 1;		/* no user waiting */
990	if (sc->event_count == APM_NEVENTS)
991		return 1;			/* overflow */
992	if (sc->event_filter[event_type] == 0)
993		return 1;		/* not registered */
994	evp = &sc->event_list[sc->event_ptr];
995	sc->event_count++;
996	sc->event_ptr++;
997	sc->event_ptr %= APM_NEVENTS;
998	evp->type = event_type;
999	evp->index = ++apm_evindex;
1000	selwakeuppri(&sc->sc_rsel, PZERO);
1001	return (sc->sc_flags & SCFLAG_OCTL) ? 0 : 1; /* user may handle */
1002}
1003
1004/* Power profile */
1005static void
1006apm_power_profile(struct apm_softc *sc)
1007{
1008	int state;
1009	struct apm_info info;
1010	static int apm_acline = 0;
1011
1012	if (apm_get_info(&info))
1013		return;
1014
1015	if (apm_acline != info.ai_acline) {
1016		apm_acline = info.ai_acline;
1017		state = apm_acline ? POWER_PROFILE_PERFORMANCE : POWER_PROFILE_ECONOMY;
1018		power_profile_set_state(state);
1019	}
1020}
1021
1022/* Process APM event */
1023static void
1024apm_processevent(void)
1025{
1026	int apm_event;
1027	struct apm_softc *sc = &apm_softc;
1028
1029#define OPMEV_DEBUGMESSAGE(symbol) case symbol:				\
1030	APM_DPRINT("Received APM Event: " #symbol "\n");
1031
1032	do {
1033		apm_event = apm_getevent();
1034		switch (apm_event) {
1035		    OPMEV_DEBUGMESSAGE(PMEV_STANDBYREQ);
1036			if (apm_op_inprog == 0) {
1037			    apm_op_inprog++;
1038			    if (apm_record_event(sc, apm_event)) {
1039				apm_suspend(PMST_STANDBY);
1040			    }
1041			}
1042			break;
1043		    OPMEV_DEBUGMESSAGE(PMEV_USERSTANDBYREQ);
1044			if (apm_op_inprog == 0) {
1045			    apm_op_inprog++;
1046			    if (apm_record_event(sc, apm_event)) {
1047				apm_suspend(PMST_STANDBY);
1048			    }
1049			}
1050			break;
1051		    OPMEV_DEBUGMESSAGE(PMEV_SUSPENDREQ);
1052 			apm_lastreq_notify();
1053			if (apm_op_inprog == 0) {
1054			    apm_op_inprog++;
1055			    if (apm_record_event(sc, apm_event)) {
1056				apm_do_suspend();
1057			    }
1058			}
1059			return; /* XXX skip the rest */
1060		    OPMEV_DEBUGMESSAGE(PMEV_USERSUSPENDREQ);
1061 			apm_lastreq_notify();
1062			if (apm_op_inprog == 0) {
1063			    apm_op_inprog++;
1064			    if (apm_record_event(sc, apm_event)) {
1065				apm_do_suspend();
1066			    }
1067			}
1068			return; /* XXX skip the rest */
1069		    OPMEV_DEBUGMESSAGE(PMEV_CRITSUSPEND);
1070			apm_do_suspend();
1071			break;
1072		    OPMEV_DEBUGMESSAGE(PMEV_NORMRESUME);
1073			apm_record_event(sc, apm_event);
1074			apm_resume();
1075			break;
1076		    OPMEV_DEBUGMESSAGE(PMEV_CRITRESUME);
1077			apm_record_event(sc, apm_event);
1078			apm_resume();
1079			break;
1080		    OPMEV_DEBUGMESSAGE(PMEV_STANDBYRESUME);
1081			apm_record_event(sc, apm_event);
1082			break;
1083		    OPMEV_DEBUGMESSAGE(PMEV_BATTERYLOW);
1084			if (apm_record_event(sc, apm_event)) {
1085			    apm_battery_low();
1086			    apm_suspend(PMST_SUSPEND);
1087			}
1088			break;
1089		    OPMEV_DEBUGMESSAGE(PMEV_POWERSTATECHANGE);
1090			apm_record_event(sc, apm_event);
1091			apm_power_profile(sc);
1092			break;
1093		    OPMEV_DEBUGMESSAGE(PMEV_UPDATETIME);
1094			apm_record_event(sc, apm_event);
1095			inittodr(0);	/* adjust time to RTC */
1096			break;
1097		    OPMEV_DEBUGMESSAGE(PMEV_CAPABILITIESCHANGE);
1098			apm_record_event(sc, apm_event);
1099			apm_power_profile(sc);
1100			break;
1101		    case PMEV_NOEVENT:
1102			break;
1103		    default:
1104			printf("Unknown Original APM Event 0x%x\n", apm_event);
1105			    break;
1106		}
1107	} while (apm_event != PMEV_NOEVENT);
1108#ifdef PC98
1109	apm_disable_smm(sc);
1110#endif
1111}
1112
1113/*
1114 * Attach APM:
1115 *
1116 * Initialize APM driver
1117 */
1118
1119static int
1120apm_attach(device_t dev)
1121{
1122	struct apm_softc	*sc = &apm_softc;
1123	int			flags;
1124	int			drv_version;
1125#ifdef PC98
1126	int			rid;
1127#endif
1128
1129	if (resource_int_value("apm", 0, "flags", &flags) != 0)
1130		flags = 0;
1131
1132	if (flags & 0x20)
1133		statclock_disable = 1;
1134
1135	sc->initialized = 0;
1136
1137	/* Must be externally enabled */
1138	sc->active = 0;
1139
1140	/* Always call HLT in idle loop */
1141	sc->always_halt_cpu = 1;
1142
1143	getenv_int("debug.apm_debug", &apm_debug);
1144
1145	/* print bootstrap messages */
1146	APM_DPRINT("apm: APM BIOS version %04lx\n",  apm_version);
1147	APM_DPRINT("apm: Code16 0x%08x, Data 0x%08x\n",
1148	    sc->bios.seg.code16.base, sc->bios.seg.data.base);
1149	APM_DPRINT("apm: Code entry 0x%08x, Idling CPU %s, Management %s\n",
1150	    sc->bios.entry, is_enabled(sc->slow_idle_cpu),
1151	    is_enabled(!sc->disabled));
1152	APM_DPRINT("apm: CS_limit=0x%x, DS_limit=0x%x\n",
1153	    sc->bios.seg.code16.limit, sc->bios.seg.data.limit);
1154
1155#ifdef PC98
1156	rid = 0;
1157	sc->sc_res = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid,
1158			 APM_NECSMM_PORT, ~0, APM_NECSMM_PORTSZ, RF_ACTIVE);
1159	if (sc->sc_res == NULL)
1160		panic("%s: counldn't map I/O ports", device_get_name(dev));
1161	sc->sc_iot = rman_get_bustag(sc->sc_res);
1162	sc->sc_ioh = rman_get_bushandle(sc->sc_res);
1163
1164	if (apm_version==0x112 || apm_version==0x111 || apm_version==0x110)
1165		apm_necsmm_addr = APM_NECSMM_PORT;
1166	else
1167		apm_necsmm_addr = 0;
1168	apm_necsmm_mask = ~APM_NECSMM_EN;
1169#endif /* PC98 */
1170
1171	/*
1172         * In one test, apm bios version was 1.02; an attempt to register
1173         * a 1.04 driver resulted in a 1.00 connection!  Registering a
1174         * 1.02 driver resulted in a 1.02 connection.
1175         */
1176	drv_version = apm_version > 0x102 ? 0x102 : apm_version;
1177	for (; drv_version > 0x100; drv_version--)
1178		if (apm_driver_version(drv_version) == 0)
1179			break;
1180	sc->minorversion = ((drv_version & 0x00f0) >>  4) * 10 +
1181		((drv_version & 0x000f) >> 0);
1182	sc->majorversion = ((drv_version & 0xf000) >> 12) * 10 +
1183		((apm_version & 0x0f00) >> 8);
1184
1185	sc->intversion = INTVERSION(sc->majorversion, sc->minorversion);
1186
1187	if (sc->intversion >= INTVERSION(1, 1))
1188		APM_DPRINT("apm: Engaged control %s\n", is_enabled(!sc->disengaged));
1189	device_printf(dev, "found APM BIOS v%ld.%ld, connected at v%d.%d\n",
1190	       ((apm_version & 0xf000) >> 12) * 10 + ((apm_version & 0x0f00) >> 8),
1191	       ((apm_version & 0x00f0) >> 4) * 10 + ((apm_version & 0x000f) >> 0),
1192	       sc->majorversion, sc->minorversion);
1193
1194
1195	APM_DPRINT("apm: Slow Idling CPU %s\n", is_enabled(sc->slow_idle_cpu));
1196	/* enable power management */
1197	if (sc->disabled) {
1198		if (apm_enable_disable_pm(1)) {
1199			APM_DPRINT("apm: *Warning* enable function failed! [%x]\n",
1200			    (sc->bios.r.eax >> 8) & 0xff);
1201		}
1202	}
1203
1204	/* engage power managment (APM 1.1 or later) */
1205	if (sc->intversion >= INTVERSION(1, 1) && sc->disengaged) {
1206		if (apm_engage_disengage_pm(1)) {
1207			APM_DPRINT("apm: *Warning* engage function failed err=[%x]",
1208			    (sc->bios.r.eax >> 8) & 0xff);
1209			APM_DPRINT(" (Docked or using external power?).\n");
1210		}
1211	}
1212
1213	/* Power the system off using APM */
1214	EVENTHANDLER_REGISTER(shutdown_final, apm_power_off, NULL,
1215			      SHUTDOWN_PRI_LAST);
1216
1217	/* Register APM again to pass the correct argument of pm_func. */
1218	power_pm_register(POWER_PM_TYPE_APM, apm_pm_func, sc);
1219
1220	sc->initialized = 1;
1221	sc->suspending = 0;
1222
1223	make_dev(&apm_cdevsw, 0, 0, 5, 0664, "apm");
1224	make_dev(&apm_cdevsw, 8, 0, 5, 0660, "apmctl");
1225	return 0;
1226}
1227
1228static int
1229apmopen(dev_t dev, int flag, int fmt, struct thread *td)
1230{
1231	struct apm_softc *sc = &apm_softc;
1232	int ctl = APMDEV(dev);
1233
1234	if (!sc->initialized)
1235		return (ENXIO);
1236
1237	switch (ctl) {
1238	case APMDEV_CTL:
1239		if (!(flag & FWRITE))
1240			return EINVAL;
1241		if (sc->sc_flags & SCFLAG_OCTL)
1242			return EBUSY;
1243		sc->sc_flags |= SCFLAG_OCTL;
1244		bzero(sc->event_filter, sizeof sc->event_filter);
1245		break;
1246	case APMDEV_NORMAL:
1247		sc->sc_flags |= SCFLAG_ONORMAL;
1248		break;
1249	default:
1250		return ENXIO;
1251		break;
1252	}
1253	return 0;
1254}
1255
1256static int
1257apmclose(dev_t dev, int flag, int fmt, struct thread *td)
1258{
1259	struct apm_softc *sc = &apm_softc;
1260	int ctl = APMDEV(dev);
1261
1262	switch (ctl) {
1263	case APMDEV_CTL:
1264		apm_lastreq_rejected();
1265		sc->sc_flags &= ~SCFLAG_OCTL;
1266		bzero(sc->event_filter, sizeof sc->event_filter);
1267		break;
1268	case APMDEV_NORMAL:
1269		sc->sc_flags &= ~SCFLAG_ONORMAL;
1270		break;
1271	}
1272	if ((sc->sc_flags & SCFLAG_OPEN) == 0) {
1273		sc->event_count = 0;
1274		sc->event_ptr = 0;
1275	}
1276	return 0;
1277}
1278
1279static int
1280apmioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct thread *td)
1281{
1282	struct apm_softc *sc = &apm_softc;
1283	struct apm_bios_arg *args;
1284	int error = 0;
1285	int ret;
1286	int newstate;
1287
1288	if (!sc->initialized)
1289		return (ENXIO);
1290	APM_DPRINT("APM ioctl: cmd = 0x%lx\n", cmd);
1291	switch (cmd) {
1292	case APMIO_SUSPEND:
1293		if (!(flag & FWRITE))
1294			return (EPERM);
1295		if (sc->active)
1296			apm_suspend(PMST_SUSPEND);
1297		else
1298			error = EINVAL;
1299		break;
1300
1301	case APMIO_STANDBY:
1302		if (!(flag & FWRITE))
1303			return (EPERM);
1304		if (sc->active)
1305			apm_suspend(PMST_STANDBY);
1306		else
1307			error = EINVAL;
1308		break;
1309
1310	case APMIO_GETINFO_OLD:
1311		{
1312			struct apm_info info;
1313			apm_info_old_t aiop;
1314
1315			if (apm_get_info(&info))
1316				error = ENXIO;
1317			aiop = (apm_info_old_t)addr;
1318			aiop->ai_major = info.ai_major;
1319			aiop->ai_minor = info.ai_minor;
1320			aiop->ai_acline = info.ai_acline;
1321			aiop->ai_batt_stat = info.ai_batt_stat;
1322			aiop->ai_batt_life = info.ai_batt_life;
1323			aiop->ai_status = info.ai_status;
1324		}
1325		break;
1326	case APMIO_GETINFO:
1327		if (apm_get_info((apm_info_t)addr))
1328			error = ENXIO;
1329		break;
1330	case APMIO_GETPWSTATUS:
1331		if (apm_get_pwstatus((apm_pwstatus_t)addr))
1332			error = ENXIO;
1333		break;
1334	case APMIO_ENABLE:
1335		if (!(flag & FWRITE))
1336			return (EPERM);
1337		apm_event_enable();
1338		break;
1339	case APMIO_DISABLE:
1340		if (!(flag & FWRITE))
1341			return (EPERM);
1342		apm_event_disable();
1343		break;
1344	case APMIO_HALTCPU:
1345		if (!(flag & FWRITE))
1346			return (EPERM);
1347		apm_halt_cpu();
1348		break;
1349	case APMIO_NOTHALTCPU:
1350		if (!(flag & FWRITE))
1351			return (EPERM);
1352		apm_not_halt_cpu();
1353		break;
1354	case APMIO_DISPLAY:
1355		if (!(flag & FWRITE))
1356			return (EPERM);
1357		newstate = *(int *)addr;
1358		if (apm_display(newstate))
1359			error = ENXIO;
1360		break;
1361	case APMIO_BIOS:
1362		if (!(flag & FWRITE))
1363			return (EPERM);
1364		/* XXX compatibility with the old interface */
1365		args = (struct apm_bios_arg *)addr;
1366		sc->bios.r.eax = args->eax;
1367		sc->bios.r.ebx = args->ebx;
1368		sc->bios.r.ecx = args->ecx;
1369		sc->bios.r.edx = args->edx;
1370		sc->bios.r.esi = args->esi;
1371		sc->bios.r.edi = args->edi;
1372		if ((ret = apm_bioscall())) {
1373			/*
1374			 * Return code 1 means bios call was unsuccessful.
1375			 * Error code is stored in %ah.
1376			 * Return code -1 means bios call was unsupported
1377			 * in the APM BIOS version.
1378			 */
1379			if (ret == -1) {
1380				error = EINVAL;
1381			}
1382		} else {
1383			/*
1384			 * Return code 0 means bios call was successful.
1385			 * We need only %al and can discard %ah.
1386			 */
1387			sc->bios.r.eax &= 0xff;
1388		}
1389		args->eax = sc->bios.r.eax;
1390		args->ebx = sc->bios.r.ebx;
1391		args->ecx = sc->bios.r.ecx;
1392		args->edx = sc->bios.r.edx;
1393		args->esi = sc->bios.r.esi;
1394		args->edi = sc->bios.r.edi;
1395		break;
1396	default:
1397		error = EINVAL;
1398		break;
1399	}
1400
1401	/* for /dev/apmctl */
1402	if (APMDEV(dev) == APMDEV_CTL) {
1403		struct apm_event_info *evp;
1404		int i;
1405
1406		error = 0;
1407		switch (cmd) {
1408		case APMIO_NEXTEVENT:
1409			if (!sc->event_count) {
1410				error = EAGAIN;
1411			} else {
1412				evp = (struct apm_event_info *)addr;
1413				i = sc->event_ptr + APM_NEVENTS - sc->event_count;
1414				i %= APM_NEVENTS;
1415				*evp = sc->event_list[i];
1416				sc->event_count--;
1417			}
1418			break;
1419		case APMIO_REJECTLASTREQ:
1420			if (apm_lastreq_rejected()) {
1421				error = EINVAL;
1422			}
1423			break;
1424		default:
1425			error = EINVAL;
1426			break;
1427		}
1428	}
1429
1430	return error;
1431}
1432
1433static int
1434apmwrite(dev_t dev, struct uio *uio, int ioflag)
1435{
1436	struct apm_softc *sc = &apm_softc;
1437	u_int event_type;
1438	int error;
1439	u_char enabled;
1440
1441	if (APMDEV(dev) != APMDEV_CTL)
1442		return(ENODEV);
1443	if (uio->uio_resid != sizeof(u_int))
1444		return(E2BIG);
1445
1446	if ((error = uiomove((caddr_t)&event_type, sizeof(u_int), uio)))
1447		return(error);
1448
1449	if (event_type < 0 || event_type >= APM_NPMEV)
1450		return(EINVAL);
1451
1452	if (sc->event_filter[event_type] == 0) {
1453		enabled = 1;
1454	} else {
1455		enabled = 0;
1456	}
1457	sc->event_filter[event_type] = enabled;
1458	APM_DPRINT("apmwrite: event 0x%x %s\n", event_type, is_enabled(enabled));
1459
1460	return uio->uio_resid;
1461}
1462
1463static int
1464apmpoll(dev_t dev, int events, struct thread *td)
1465{
1466	struct apm_softc *sc = &apm_softc;
1467	int revents = 0;
1468
1469	if (events & (POLLIN | POLLRDNORM)) {
1470		if (sc->event_count) {
1471			revents |= events & (POLLIN | POLLRDNORM);
1472		} else {
1473			selrecord(td, &sc->sc_rsel);
1474		}
1475	}
1476
1477	return (revents);
1478}
1479
1480static device_method_t apm_methods[] = {
1481	/* Device interface */
1482	DEVMETHOD(device_identify,	apm_identify),
1483	DEVMETHOD(device_probe,		apm_probe),
1484	DEVMETHOD(device_attach,	apm_attach),
1485
1486	{ 0, 0 }
1487};
1488
1489static driver_t apm_driver = {
1490	"apm",
1491	apm_methods,
1492	1,			/* no softc (XXX) */
1493};
1494
1495static devclass_t apm_devclass;
1496
1497DRIVER_MODULE(apm, legacy, apm_driver, apm_devclass, apm_modevent, 0);
1498MODULE_VERSION(apm, 1);
1499
1500static int
1501apm_pm_func(u_long cmd, void *arg, ...)
1502{
1503	int	state, apm_state;
1504	int	error;
1505	va_list	ap;
1506
1507	error = 0;
1508	switch (cmd) {
1509	case POWER_CMD_SUSPEND:
1510		va_start(ap, arg);
1511		state = va_arg(ap, int);
1512		va_end(ap);
1513
1514		switch (state) {
1515		case POWER_SLEEP_STATE_STANDBY:
1516			apm_state = PMST_STANDBY;
1517			break;
1518		case POWER_SLEEP_STATE_SUSPEND:
1519		case POWER_SLEEP_STATE_HIBERNATE:
1520			apm_state = PMST_SUSPEND;
1521			break;
1522		default:
1523			error = EINVAL;
1524			goto out;
1525		}
1526
1527		apm_suspend(apm_state);
1528		break;
1529
1530	default:
1531		error = EINVAL;
1532		goto out;
1533	}
1534
1535out:
1536	return (error);
1537}
1538
1539static void
1540apm_pm_register(void *arg)
1541{
1542
1543	if (!resource_disabled("apm", 0))
1544		power_pm_register(POWER_PM_TYPE_APM, apm_pm_func, NULL);
1545}
1546
1547SYSINIT(power, SI_SUB_KLD, SI_ORDER_ANY, apm_pm_register, 0);
1548