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