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