apm.c revision 136520
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 136520 2004-10-14 22:21:59Z njl $");
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 = 0xffffffff;	/* 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 & 0xff;
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#ifdef PC98
868	int			rid;
869#endif
870
871	device_set_desc(dev, "APM BIOS");
872	if (device_get_unit(dev) > 0) {
873		printf("apm: Only one APM driver supported.\n");
874		return ENXIO;
875	}
876
877	if (power_pm_get_type() != POWER_PM_TYPE_NONE &&
878	    power_pm_get_type() != POWER_PM_TYPE_APM) {
879		printf("apm: Other PM system enabled.\n");
880		return ENXIO;
881	}
882
883	bzero(&vmf, sizeof(struct vm86frame));		/* safety */
884	bzero(&apm_softc, sizeof(apm_softc));
885	vmf.vmf_ah = APM_BIOS;
886	vmf.vmf_al = APM_INSTCHECK;
887	vmf.vmf_bx = 0;
888	if (vm86_intcall(APM_INT, &vmf))
889		return ENXIO;			/* APM not found */
890	if (vmf.vmf_bx != 0x504d) {
891		printf("apm: incorrect signature (0x%x)\n", vmf.vmf_bx);
892		return ENXIO;
893	}
894	if ((vmf.vmf_cx & (APM_32BIT_SUPPORT | APM_16BIT_SUPPORT)) == 0) {
895		printf("apm: protected mode connections are not supported\n");
896		return ENXIO;
897	}
898
899	apm_version = vmf.vmf_ax;
900	sc->slow_idle_cpu = ((vmf.vmf_cx & APM_CPUIDLE_SLOW) != 0);
901	sc->disabled = ((vmf.vmf_cx & APM_DISABLED) != 0);
902	sc->disengaged = ((vmf.vmf_cx & APM_DISENGAGED) != 0);
903
904	vmf.vmf_ah = APM_BIOS;
905	vmf.vmf_al = APM_DISCONNECT;
906	vmf.vmf_bx = 0;
907        vm86_intcall(APM_INT, &vmf);		/* disconnect, just in case */
908
909#ifdef PC98
910	/* PC98 have bogos APM 32bit BIOS */
911	if ((vmf.vmf_cx & APM_32BIT_SUPPORT) == 0)
912		return ENXIO;
913	rid = 0;
914	bus_set_resource(dev, SYS_RES_IOPORT, rid,
915			 APM_NECSMM_PORT, APM_NECSMM_PORTSZ);
916	sc->sc_res = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid,
917			 APM_NECSMM_PORT, ~0, APM_NECSMM_PORTSZ, RF_ACTIVE);
918	if (sc->sc_res == NULL) {
919		printf("apm: cannot open NEC smm device\n");
920		return ENXIO;
921	}
922	bus_release_resource(dev, SYS_RES_IOPORT, rid, sc->sc_res);
923
924	vmf.vmf_ah = APM_BIOS;
925	vmf.vmf_al = APM_PROT32CONNECT;
926	vmf.vmf_bx = 0;
927	if (vm86_intcall(APM_INT, &vmf)) {
928		printf("apm: 32-bit connection error.\n");
929		return (ENXIO);
930 	}
931
932	sc->bios.seg.code32.base = (vmf.vmf_ax << 4) + APM_KERNBASE;
933	sc->bios.seg.code32.limit = 0xffff;
934	sc->bios.seg.code16.base = (vmf.vmf_cx << 4) + APM_KERNBASE;
935	sc->bios.seg.code16.limit = 0xffff;
936	sc->bios.seg.data.base = (vmf.vmf_dx << 4) + APM_KERNBASE;
937	sc->bios.seg.data.limit = 0xffff;
938	sc->bios.entry = vmf.vmf_ebx;
939	sc->connectmode = APM_PROT32CONNECT;
940#else
941	if ((vmf.vmf_cx & APM_32BIT_SUPPORT) != 0) {
942		vmf.vmf_ah = APM_BIOS;
943		vmf.vmf_al = APM_PROT32CONNECT;
944		vmf.vmf_bx = 0;
945		if (vm86_intcall(APM_INT, &vmf)) {
946			printf("apm: 32-bit connection error.\n");
947			return (ENXIO);
948 		}
949		sc->bios.seg.code32.base = (vmf.vmf_ax << 4) + APM_KERNBASE;
950		sc->bios.seg.code32.limit = 0xffff;
951		sc->bios.seg.code16.base = (vmf.vmf_cx << 4) + APM_KERNBASE;
952		sc->bios.seg.code16.limit = 0xffff;
953		sc->bios.seg.data.base = (vmf.vmf_dx << 4) + APM_KERNBASE;
954		sc->bios.seg.data.limit = 0xffff;
955		sc->bios.entry = vmf.vmf_ebx;
956		sc->connectmode = APM_PROT32CONNECT;
957 	} else {
958		/* use 16-bit connection */
959		vmf.vmf_ah = APM_BIOS;
960		vmf.vmf_al = APM_PROT16CONNECT;
961		vmf.vmf_bx = 0;
962		if (vm86_intcall(APM_INT, &vmf)) {
963			printf("apm: 16-bit connection error.\n");
964			return (ENXIO);
965		}
966		sc->bios.seg.code16.base = (vmf.vmf_ax << 4) + APM_KERNBASE;
967		sc->bios.seg.code16.limit = 0xffff;
968		sc->bios.seg.data.base = (vmf.vmf_cx << 4) + APM_KERNBASE;
969		sc->bios.seg.data.limit = 0xffff;
970		sc->bios.entry = vmf.vmf_bx;
971		sc->connectmode = APM_PROT16CONNECT;
972	}
973#endif
974	return(0);
975}
976
977
978/*
979 * return 0 if the user will notice and handle the event,
980 * return 1 if the kernel driver should do so.
981 */
982static int
983apm_record_event(struct apm_softc *sc, u_int event_type)
984{
985	struct apm_event_info *evp;
986
987	if ((sc->sc_flags & SCFLAG_OPEN) == 0)
988		return 1;		/* no user waiting */
989	if (sc->event_count == APM_NEVENTS)
990		return 1;			/* overflow */
991	if (sc->event_filter[event_type] == 0)
992		return 1;		/* not registered */
993	evp = &sc->event_list[sc->event_ptr];
994	sc->event_count++;
995	sc->event_ptr++;
996	sc->event_ptr %= APM_NEVENTS;
997	evp->type = event_type;
998	evp->index = ++apm_evindex;
999	selwakeuppri(&sc->sc_rsel, PZERO);
1000	return (sc->sc_flags & SCFLAG_OCTL) ? 0 : 1; /* user may handle */
1001}
1002
1003/* Power profile */
1004static void
1005apm_power_profile(struct apm_softc *sc)
1006{
1007	int state;
1008	struct apm_info info;
1009	static int apm_acline = 0;
1010
1011	if (apm_get_info(&info))
1012		return;
1013
1014	if (apm_acline != info.ai_acline) {
1015		apm_acline = info.ai_acline;
1016		state = apm_acline ? POWER_PROFILE_PERFORMANCE : POWER_PROFILE_ECONOMY;
1017		power_profile_set_state(state);
1018	}
1019}
1020
1021/* Process APM event */
1022static void
1023apm_processevent(void)
1024{
1025	int apm_event;
1026	struct apm_softc *sc = &apm_softc;
1027
1028#define OPMEV_DEBUGMESSAGE(symbol) case symbol:				\
1029	APM_DPRINT("Received APM Event: " #symbol "\n");
1030
1031	do {
1032		apm_event = apm_getevent();
1033		switch (apm_event) {
1034		    OPMEV_DEBUGMESSAGE(PMEV_STANDBYREQ);
1035			if (apm_op_inprog == 0) {
1036			    apm_op_inprog++;
1037			    if (apm_record_event(sc, apm_event)) {
1038				apm_suspend(PMST_STANDBY);
1039			    }
1040			}
1041			break;
1042		    OPMEV_DEBUGMESSAGE(PMEV_USERSTANDBYREQ);
1043			if (apm_op_inprog == 0) {
1044			    apm_op_inprog++;
1045			    if (apm_record_event(sc, apm_event)) {
1046				apm_suspend(PMST_STANDBY);
1047			    }
1048			}
1049			break;
1050		    OPMEV_DEBUGMESSAGE(PMEV_SUSPENDREQ);
1051 			apm_lastreq_notify();
1052			if (apm_op_inprog == 0) {
1053			    apm_op_inprog++;
1054			    if (apm_record_event(sc, apm_event)) {
1055				apm_do_suspend();
1056			    }
1057			}
1058			return; /* XXX skip the rest */
1059		    OPMEV_DEBUGMESSAGE(PMEV_USERSUSPENDREQ);
1060 			apm_lastreq_notify();
1061			if (apm_op_inprog == 0) {
1062			    apm_op_inprog++;
1063			    if (apm_record_event(sc, apm_event)) {
1064				apm_do_suspend();
1065			    }
1066			}
1067			return; /* XXX skip the rest */
1068		    OPMEV_DEBUGMESSAGE(PMEV_CRITSUSPEND);
1069			apm_do_suspend();
1070			break;
1071		    OPMEV_DEBUGMESSAGE(PMEV_NORMRESUME);
1072			apm_record_event(sc, apm_event);
1073			apm_resume();
1074			break;
1075		    OPMEV_DEBUGMESSAGE(PMEV_CRITRESUME);
1076			apm_record_event(sc, apm_event);
1077			apm_resume();
1078			break;
1079		    OPMEV_DEBUGMESSAGE(PMEV_STANDBYRESUME);
1080			apm_record_event(sc, apm_event);
1081			break;
1082		    OPMEV_DEBUGMESSAGE(PMEV_BATTERYLOW);
1083			if (apm_record_event(sc, apm_event)) {
1084			    apm_battery_low();
1085			    apm_suspend(PMST_SUSPEND);
1086			}
1087			break;
1088		    OPMEV_DEBUGMESSAGE(PMEV_POWERSTATECHANGE);
1089			apm_record_event(sc, apm_event);
1090			apm_power_profile(sc);
1091			break;
1092		    OPMEV_DEBUGMESSAGE(PMEV_UPDATETIME);
1093			apm_record_event(sc, apm_event);
1094			inittodr(0);	/* adjust time to RTC */
1095			break;
1096		    OPMEV_DEBUGMESSAGE(PMEV_CAPABILITIESCHANGE);
1097			apm_record_event(sc, apm_event);
1098			apm_power_profile(sc);
1099			break;
1100		    case PMEV_NOEVENT:
1101			break;
1102		    default:
1103			printf("Unknown Original APM Event 0x%x\n", apm_event);
1104			    break;
1105		}
1106	} while (apm_event != PMEV_NOEVENT);
1107#ifdef PC98
1108	apm_disable_smm(sc);
1109#endif
1110}
1111
1112/*
1113 * Attach APM:
1114 *
1115 * Initialize APM driver
1116 */
1117
1118static int
1119apm_attach(device_t dev)
1120{
1121	struct apm_softc	*sc = &apm_softc;
1122	int			drv_version;
1123#ifdef PC98
1124	int			rid;
1125#endif
1126
1127	if (device_get_flags(dev) & 0x20)
1128		statclock_disable = 1;
1129
1130	sc->initialized = 0;
1131
1132	/* Must be externally enabled */
1133	sc->active = 0;
1134
1135	/* Always call HLT in idle loop */
1136	sc->always_halt_cpu = 1;
1137
1138	getenv_int("debug.apm_debug", &apm_debug);
1139
1140	/* print bootstrap messages */
1141	APM_DPRINT("apm: APM BIOS version %04lx\n",  apm_version);
1142	APM_DPRINT("apm: Code16 0x%08x, Data 0x%08x\n",
1143	    sc->bios.seg.code16.base, sc->bios.seg.data.base);
1144	APM_DPRINT("apm: Code entry 0x%08x, Idling CPU %s, Management %s\n",
1145	    sc->bios.entry, is_enabled(sc->slow_idle_cpu),
1146	    is_enabled(!sc->disabled));
1147	APM_DPRINT("apm: CS_limit=0x%x, DS_limit=0x%x\n",
1148	    sc->bios.seg.code16.limit, sc->bios.seg.data.limit);
1149
1150#ifdef PC98
1151	rid = 0;
1152	sc->sc_res = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid,
1153			 APM_NECSMM_PORT, ~0, APM_NECSMM_PORTSZ, RF_ACTIVE);
1154	if (sc->sc_res == NULL)
1155		panic("%s: counldn't map I/O ports", device_get_name(dev));
1156	sc->sc_iot = rman_get_bustag(sc->sc_res);
1157	sc->sc_ioh = rman_get_bushandle(sc->sc_res);
1158
1159	if (apm_version==0x112 || apm_version==0x111 || apm_version==0x110)
1160		apm_necsmm_addr = APM_NECSMM_PORT;
1161	else
1162		apm_necsmm_addr = 0;
1163	apm_necsmm_mask = ~APM_NECSMM_EN;
1164#endif /* PC98 */
1165
1166	/*
1167         * In one test, apm bios version was 1.02; an attempt to register
1168         * a 1.04 driver resulted in a 1.00 connection!  Registering a
1169         * 1.02 driver resulted in a 1.02 connection.
1170         */
1171	drv_version = apm_version > 0x102 ? 0x102 : apm_version;
1172	for (; drv_version > 0x100; drv_version--)
1173		if (apm_driver_version(drv_version) == 0)
1174			break;
1175	sc->minorversion = ((drv_version & 0x00f0) >>  4) * 10 +
1176		((drv_version & 0x000f) >> 0);
1177	sc->majorversion = ((drv_version & 0xf000) >> 12) * 10 +
1178		((apm_version & 0x0f00) >> 8);
1179
1180	sc->intversion = INTVERSION(sc->majorversion, sc->minorversion);
1181
1182	if (sc->intversion >= INTVERSION(1, 1))
1183		APM_DPRINT("apm: Engaged control %s\n", is_enabled(!sc->disengaged));
1184	device_printf(dev, "found APM BIOS v%ld.%ld, connected at v%d.%d\n",
1185	       ((apm_version & 0xf000) >> 12) * 10 + ((apm_version & 0x0f00) >> 8),
1186	       ((apm_version & 0x00f0) >> 4) * 10 + ((apm_version & 0x000f) >> 0),
1187	       sc->majorversion, sc->minorversion);
1188
1189
1190	APM_DPRINT("apm: Slow Idling CPU %s\n", is_enabled(sc->slow_idle_cpu));
1191	/* enable power management */
1192	if (sc->disabled) {
1193		if (apm_enable_disable_pm(1)) {
1194			APM_DPRINT("apm: *Warning* enable function failed! [%x]\n",
1195			    (sc->bios.r.eax >> 8) & 0xff);
1196		}
1197	}
1198
1199	/* engage power managment (APM 1.1 or later) */
1200	if (sc->intversion >= INTVERSION(1, 1) && sc->disengaged) {
1201		if (apm_engage_disengage_pm(1)) {
1202			APM_DPRINT("apm: *Warning* engage function failed err=[%x]",
1203			    (sc->bios.r.eax >> 8) & 0xff);
1204			APM_DPRINT(" (Docked or using external power?).\n");
1205		}
1206	}
1207
1208	/* Power the system off using APM */
1209	EVENTHANDLER_REGISTER(shutdown_final, apm_power_off, NULL,
1210			      SHUTDOWN_PRI_LAST);
1211
1212	/* Register APM again to pass the correct argument of pm_func. */
1213	power_pm_register(POWER_PM_TYPE_APM, apm_pm_func, sc);
1214
1215	sc->initialized = 1;
1216	sc->suspending = 0;
1217
1218	make_dev(&apm_cdevsw, 0, 0, 5, 0664, "apm");
1219	make_dev(&apm_cdevsw, 8, 0, 5, 0660, "apmctl");
1220	return 0;
1221}
1222
1223static int
1224apmopen(struct cdev *dev, int flag, int fmt, struct thread *td)
1225{
1226	struct apm_softc *sc = &apm_softc;
1227	int ctl = APMDEV(dev);
1228
1229	if (!sc->initialized)
1230		return (ENXIO);
1231
1232	switch (ctl) {
1233	case APMDEV_CTL:
1234		if (!(flag & FWRITE))
1235			return EINVAL;
1236		if (sc->sc_flags & SCFLAG_OCTL)
1237			return EBUSY;
1238		sc->sc_flags |= SCFLAG_OCTL;
1239		bzero(sc->event_filter, sizeof sc->event_filter);
1240		break;
1241	case APMDEV_NORMAL:
1242		sc->sc_flags |= SCFLAG_ONORMAL;
1243		break;
1244	default:
1245		return ENXIO;
1246		break;
1247	}
1248	return 0;
1249}
1250
1251static int
1252apmclose(struct cdev *dev, int flag, int fmt, struct thread *td)
1253{
1254	struct apm_softc *sc = &apm_softc;
1255	int ctl = APMDEV(dev);
1256
1257	switch (ctl) {
1258	case APMDEV_CTL:
1259		apm_lastreq_rejected();
1260		sc->sc_flags &= ~SCFLAG_OCTL;
1261		bzero(sc->event_filter, sizeof sc->event_filter);
1262		break;
1263	case APMDEV_NORMAL:
1264		sc->sc_flags &= ~SCFLAG_ONORMAL;
1265		break;
1266	}
1267	if ((sc->sc_flags & SCFLAG_OPEN) == 0) {
1268		sc->event_count = 0;
1269		sc->event_ptr = 0;
1270	}
1271	return 0;
1272}
1273
1274static int
1275apmioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, struct thread *td)
1276{
1277	struct apm_softc *sc = &apm_softc;
1278	struct apm_bios_arg *args;
1279	int error = 0;
1280	int ret;
1281	int newstate;
1282
1283	if (!sc->initialized)
1284		return (ENXIO);
1285	APM_DPRINT("APM ioctl: cmd = 0x%lx\n", cmd);
1286	switch (cmd) {
1287	case APMIO_SUSPEND:
1288		if (!(flag & FWRITE))
1289			return (EPERM);
1290		if (sc->active)
1291			apm_suspend(PMST_SUSPEND);
1292		else
1293			error = EINVAL;
1294		break;
1295
1296	case APMIO_STANDBY:
1297		if (!(flag & FWRITE))
1298			return (EPERM);
1299		if (sc->active)
1300			apm_suspend(PMST_STANDBY);
1301		else
1302			error = EINVAL;
1303		break;
1304
1305	case APMIO_GETINFO_OLD:
1306		{
1307			struct apm_info info;
1308			apm_info_old_t aiop;
1309
1310			if (apm_get_info(&info))
1311				error = ENXIO;
1312			aiop = (apm_info_old_t)addr;
1313			aiop->ai_major = info.ai_major;
1314			aiop->ai_minor = info.ai_minor;
1315			aiop->ai_acline = info.ai_acline;
1316			aiop->ai_batt_stat = info.ai_batt_stat;
1317			aiop->ai_batt_life = info.ai_batt_life;
1318			aiop->ai_status = info.ai_status;
1319		}
1320		break;
1321	case APMIO_GETINFO:
1322		if (apm_get_info((apm_info_t)addr))
1323			error = ENXIO;
1324		break;
1325	case APMIO_GETPWSTATUS:
1326		if (apm_get_pwstatus((apm_pwstatus_t)addr))
1327			error = ENXIO;
1328		break;
1329	case APMIO_ENABLE:
1330		if (!(flag & FWRITE))
1331			return (EPERM);
1332		apm_event_enable();
1333		break;
1334	case APMIO_DISABLE:
1335		if (!(flag & FWRITE))
1336			return (EPERM);
1337		apm_event_disable();
1338		break;
1339	case APMIO_HALTCPU:
1340		if (!(flag & FWRITE))
1341			return (EPERM);
1342		apm_halt_cpu();
1343		break;
1344	case APMIO_NOTHALTCPU:
1345		if (!(flag & FWRITE))
1346			return (EPERM);
1347		apm_not_halt_cpu();
1348		break;
1349	case APMIO_DISPLAY:
1350		if (!(flag & FWRITE))
1351			return (EPERM);
1352		newstate = *(int *)addr;
1353		if (apm_display(newstate))
1354			error = ENXIO;
1355		break;
1356	case APMIO_BIOS:
1357		if (!(flag & FWRITE))
1358			return (EPERM);
1359		/* XXX compatibility with the old interface */
1360		args = (struct apm_bios_arg *)addr;
1361		sc->bios.r.eax = args->eax;
1362		sc->bios.r.ebx = args->ebx;
1363		sc->bios.r.ecx = args->ecx;
1364		sc->bios.r.edx = args->edx;
1365		sc->bios.r.esi = args->esi;
1366		sc->bios.r.edi = args->edi;
1367		if ((ret = apm_bioscall())) {
1368			/*
1369			 * Return code 1 means bios call was unsuccessful.
1370			 * Error code is stored in %ah.
1371			 * Return code -1 means bios call was unsupported
1372			 * in the APM BIOS version.
1373			 */
1374			if (ret == -1) {
1375				error = EINVAL;
1376			}
1377		} else {
1378			/*
1379			 * Return code 0 means bios call was successful.
1380			 * We need only %al and can discard %ah.
1381			 */
1382			sc->bios.r.eax &= 0xff;
1383		}
1384		args->eax = sc->bios.r.eax;
1385		args->ebx = sc->bios.r.ebx;
1386		args->ecx = sc->bios.r.ecx;
1387		args->edx = sc->bios.r.edx;
1388		args->esi = sc->bios.r.esi;
1389		args->edi = sc->bios.r.edi;
1390		break;
1391	default:
1392		error = EINVAL;
1393		break;
1394	}
1395
1396	/* for /dev/apmctl */
1397	if (APMDEV(dev) == APMDEV_CTL) {
1398		struct apm_event_info *evp;
1399		int i;
1400
1401		error = 0;
1402		switch (cmd) {
1403		case APMIO_NEXTEVENT:
1404			if (!sc->event_count) {
1405				error = EAGAIN;
1406			} else {
1407				evp = (struct apm_event_info *)addr;
1408				i = sc->event_ptr + APM_NEVENTS - sc->event_count;
1409				i %= APM_NEVENTS;
1410				*evp = sc->event_list[i];
1411				sc->event_count--;
1412			}
1413			break;
1414		case APMIO_REJECTLASTREQ:
1415			if (apm_lastreq_rejected()) {
1416				error = EINVAL;
1417			}
1418			break;
1419		default:
1420			error = EINVAL;
1421			break;
1422		}
1423	}
1424
1425	return error;
1426}
1427
1428static int
1429apmwrite(struct cdev *dev, struct uio *uio, int ioflag)
1430{
1431	struct apm_softc *sc = &apm_softc;
1432	u_int event_type;
1433	int error;
1434	u_char enabled;
1435
1436	if (APMDEV(dev) != APMDEV_CTL)
1437		return(ENODEV);
1438	if (uio->uio_resid != sizeof(u_int))
1439		return(E2BIG);
1440
1441	if ((error = uiomove((caddr_t)&event_type, sizeof(u_int), uio)))
1442		return(error);
1443
1444	if (event_type < 0 || event_type >= APM_NPMEV)
1445		return(EINVAL);
1446
1447	if (sc->event_filter[event_type] == 0) {
1448		enabled = 1;
1449	} else {
1450		enabled = 0;
1451	}
1452	sc->event_filter[event_type] = enabled;
1453	APM_DPRINT("apmwrite: event 0x%x %s\n", event_type, is_enabled(enabled));
1454
1455	return uio->uio_resid;
1456}
1457
1458static int
1459apmpoll(struct cdev *dev, int events, struct thread *td)
1460{
1461	struct apm_softc *sc = &apm_softc;
1462	int revents = 0;
1463
1464	if (events & (POLLIN | POLLRDNORM)) {
1465		if (sc->event_count) {
1466			revents |= events & (POLLIN | POLLRDNORM);
1467		} else {
1468			selrecord(td, &sc->sc_rsel);
1469		}
1470	}
1471
1472	return (revents);
1473}
1474
1475static device_method_t apm_methods[] = {
1476	/* Device interface */
1477	DEVMETHOD(device_identify,	apm_identify),
1478	DEVMETHOD(device_probe,		apm_probe),
1479	DEVMETHOD(device_attach,	apm_attach),
1480
1481	{ 0, 0 }
1482};
1483
1484static driver_t apm_driver = {
1485	"apm",
1486	apm_methods,
1487	1,			/* no softc (XXX) */
1488};
1489
1490static devclass_t apm_devclass;
1491
1492DRIVER_MODULE(apm, legacy, apm_driver, apm_devclass, apm_modevent, 0);
1493MODULE_VERSION(apm, 1);
1494
1495static int
1496apm_pm_func(u_long cmd, void *arg, ...)
1497{
1498	int	state, apm_state;
1499	int	error;
1500	va_list	ap;
1501
1502	error = 0;
1503	switch (cmd) {
1504	case POWER_CMD_SUSPEND:
1505		va_start(ap, arg);
1506		state = va_arg(ap, int);
1507		va_end(ap);
1508
1509		switch (state) {
1510		case POWER_SLEEP_STATE_STANDBY:
1511			apm_state = PMST_STANDBY;
1512			break;
1513		case POWER_SLEEP_STATE_SUSPEND:
1514		case POWER_SLEEP_STATE_HIBERNATE:
1515			apm_state = PMST_SUSPEND;
1516			break;
1517		default:
1518			error = EINVAL;
1519			goto out;
1520		}
1521
1522		apm_suspend(apm_state);
1523		break;
1524
1525	default:
1526		error = EINVAL;
1527		goto out;
1528	}
1529
1530out:
1531	return (error);
1532}
1533
1534static void
1535apm_pm_register(void *arg)
1536{
1537
1538	if (!resource_disabled("apm", 0))
1539		power_pm_register(POWER_PM_TYPE_APM, apm_pm_func, NULL);
1540}
1541
1542SYSINIT(power, SI_SUB_KLD, SI_ORDER_ANY, apm_pm_register, 0);
1543