apm.c revision 144964
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 144964 2005-04-12 20:14:56Z mdodd $");
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 == NULL || sc->initialized == 0)
482		return;
483
484	apm_op_inprog = 0;
485	sc->suspends = sc->suspend_countdown = 0;
486
487	error = DEVICE_SUSPEND(root_bus);
488	if (error)
489		return;
490
491	apm_execute_hook(hook[APM_HOOK_SUSPEND]);
492	if (apm_suspend_system(PMST_SUSPEND) == 0) {
493		sc->suspending = 1;
494		apm_processevent();
495	} else {
496		/* Failure, 'resume' the system again */
497		apm_execute_hook(hook[APM_HOOK_RESUME]);
498		DEVICE_RESUME(root_bus);
499	}
500	return;
501}
502
503static void
504apm_do_standby(void)
505{
506	struct apm_softc *sc = &apm_softc;
507
508	if (sc == NULL || sc->initialized == 0)
509		return;
510
511	apm_op_inprog = 0;
512	sc->standbys = sc->standby_countdown = 0;
513
514	/*
515	 * As far as standby, we don't need to execute
516	 * all of suspend hooks.
517	 */
518	if (apm_suspend_system(PMST_STANDBY) == 0)
519		apm_processevent();
520	return;
521}
522
523static void
524apm_lastreq_notify(void)
525{
526	struct apm_softc *sc = &apm_softc;
527
528	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
529	sc->bios.r.ebx = PMDV_ALLDEV;
530	sc->bios.r.ecx = PMST_LASTREQNOTIFY;
531	sc->bios.r.edx = 0;
532	apm_bioscall();
533}
534
535static int
536apm_lastreq_rejected(void)
537{
538	struct apm_softc *sc = &apm_softc;
539
540	if (apm_op_inprog == 0) {
541		return 1;	/* no operation in progress */
542	}
543
544	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
545	sc->bios.r.ebx = PMDV_ALLDEV;
546	sc->bios.r.ecx = PMST_LASTREQREJECT;
547	sc->bios.r.edx = 0;
548
549	if (apm_bioscall()) {
550		APM_DPRINT("apm_lastreq_rejected: failed\n");
551		return 1;
552	}
553	apm_op_inprog = 0;
554	return 0;
555}
556
557/*
558 * Public interface to the suspend/resume:
559 *
560 * Execute suspend and resume hook before and after sleep, respectively.
561 *
562 */
563
564void
565apm_suspend(int state)
566{
567	struct apm_softc *sc = &apm_softc;
568
569	if (sc == NULL || sc->initialized == 0)
570		return;
571
572	switch (state) {
573	case PMST_SUSPEND:
574		if (sc->suspends)
575			return;
576		sc->suspends++;
577		sc->suspend_countdown = apm_suspend_delay;
578		break;
579	case PMST_STANDBY:
580		if (sc->standbys)
581			return;
582		sc->standbys++;
583		sc->standby_countdown = apm_standby_delay;
584		break;
585	default:
586		printf("apm_suspend: Unknown Suspend state 0x%x\n", state);
587		return;
588	}
589
590	apm_op_inprog++;
591	apm_lastreq_notify();
592}
593
594static void
595apm_resume(void)
596{
597	struct apm_softc *sc = &apm_softc;
598
599	if (sc == NULL || sc->initialized == 0 || sc->suspending == 0)
600		return;
601
602	sc->suspending = 0;
603	apm_execute_hook(hook[APM_HOOK_RESUME]);
604	DEVICE_RESUME(root_bus);
605	return;
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 (apm_swab_batt_minutes)
633		sc->bios.r.edx = __bswap16(sc->bios.r.edx) | 0x8000;
634	if (sc->bios.r.edx == 0xffff)	/* Time is unknown */
635		app->ap_batt_time = -1;
636	else if (sc->bios.r.edx & 0x8000)	/* Time is in minutes */
637		app->ap_batt_time = (sc->bios.r.edx & 0x7fff) * 60;
638	else				/* Time is in seconds */
639		app->ap_batt_time = sc->bios.r.edx;
640
641	return 0;
642}
643
644
645/* get APM information */
646static int
647apm_get_info(apm_info_t aip)
648{
649	struct apm_softc *sc = &apm_softc;
650	struct apm_pwstatus aps;
651
652	bzero(&aps, sizeof(aps));
653	aps.ap_device = PMDV_ALLDEV;
654	if (apm_get_pwstatus(&aps))
655		return 1;
656
657	aip->ai_infoversion = 1;
658	aip->ai_acline      = aps.ap_acline;
659	aip->ai_batt_stat   = aps.ap_batt_stat;
660	aip->ai_batt_life   = aps.ap_batt_life;
661	aip->ai_batt_time   = aps.ap_batt_time;
662	aip->ai_major       = (u_int)sc->majorversion;
663	aip->ai_minor       = (u_int)sc->minorversion;
664	aip->ai_status      = (u_int)sc->active;
665
666	sc->bios.r.eax = (APM_BIOS << 8) | APM_GETCAPABILITIES;
667	sc->bios.r.ebx = 0;
668	sc->bios.r.ecx = 0;
669	sc->bios.r.edx = 0;
670	if (apm_bioscall()) {
671		aip->ai_batteries = 0xffffffff;	/* Unknown */
672		aip->ai_capabilities = 0xff00; /* Unknown, with no bits set */
673	} else {
674		aip->ai_batteries = sc->bios.r.ebx & 0xff;
675		aip->ai_capabilities = sc->bios.r.ecx & 0xff;
676	}
677
678	bzero(aip->ai_spare, sizeof aip->ai_spare);
679
680	return 0;
681}
682
683
684/* inform APM BIOS that CPU is idle */
685void
686apm_cpu_idle(void)
687{
688	struct apm_softc *sc = &apm_softc;
689
690	if (sc->active) {
691
692		sc->bios.r.eax = (APM_BIOS <<8) | APM_CPUIDLE;
693		sc->bios.r.edx = sc->bios.r.ecx = sc->bios.r.ebx = 0;
694		(void) apm_bioscall();
695	}
696	/*
697	 * Some APM implementation halts CPU in BIOS, whenever
698	 * "CPU-idle" function are invoked, but swtch() of
699	 * FreeBSD halts CPU, therefore, CPU is halted twice
700	 * in the sched loop. It makes the interrupt latency
701	 * terribly long and be able to cause a serious problem
702	 * in interrupt processing. We prevent it by removing
703	 * "hlt" operation from swtch() and managed it under
704	 * APM driver.
705	 */
706	if (!sc->active || sc->always_halt_cpu)
707		halt();	/* wait for interrupt */
708}
709
710/* inform APM BIOS that CPU is busy */
711void
712apm_cpu_busy(void)
713{
714	struct apm_softc *sc = &apm_softc;
715
716	/*
717	 * The APM specification says this is only necessary if your BIOS
718	 * slows down the processor in the idle task, otherwise it's not
719	 * necessary.
720	 */
721	if (sc->slow_idle_cpu && sc->active) {
722
723		sc->bios.r.eax = (APM_BIOS <<8) | APM_CPUBUSY;
724		sc->bios.r.edx = sc->bios.r.ecx = sc->bios.r.ebx = 0;
725		apm_bioscall();
726	}
727}
728
729
730/*
731 * APM timeout routine:
732 *
733 * This routine is automatically called by timer once per second.
734 */
735
736static void
737apm_timeout(void *dummy)
738{
739	struct apm_softc *sc = &apm_softc;
740
741	if (apm_op_inprog)
742		apm_lastreq_notify();
743
744	if (sc->standbys && sc->standby_countdown-- <= 0)
745		apm_do_standby();
746
747	if (sc->suspends && sc->suspend_countdown-- <= 0)
748		apm_do_suspend();
749
750	if (!sc->bios_busy)
751		apm_processevent();
752
753	if (sc->active == 1)
754		/* Run slightly more oftan than 1 Hz */
755		apm_timeout_ch = timeout(apm_timeout, NULL, hz - 1);
756}
757
758/* enable APM BIOS */
759static void
760apm_event_enable(void)
761{
762	struct apm_softc *sc = &apm_softc;
763
764	APM_DPRINT("called apm_event_enable()\n");
765
766	if (sc == NULL || sc->initialized == 0)
767		return;
768
769	sc->active = 1;
770	apm_timeout(sc);
771
772	return;
773}
774
775/* disable APM BIOS */
776static void
777apm_event_disable(void)
778{
779	struct apm_softc *sc = &apm_softc;
780
781	APM_DPRINT("called apm_event_disable()\n");
782
783	if (sc == NULL || sc->initialized == 0)
784		return;
785
786	untimeout(apm_timeout, NULL, apm_timeout_ch);
787	sc->active = 0;
788
789	return;
790}
791
792/* halt CPU in scheduling loop */
793static void
794apm_halt_cpu(void)
795{
796	struct apm_softc *sc = &apm_softc;
797
798	if (sc == NULL || sc->initialized == 0)
799		return;
800
801	sc->always_halt_cpu = 1;
802
803	return;
804}
805
806/* don't halt CPU in scheduling loop */
807static void
808apm_not_halt_cpu(void)
809{
810	struct apm_softc *sc = &apm_softc;
811
812	if (sc == NULL || sc->initialized == 0)
813		return;
814
815	sc->always_halt_cpu = 0;
816
817	return;
818}
819
820/* device driver definitions */
821
822/*
823 * Module event
824 */
825
826static int
827apm_modevent(struct module *mod, int event, void *junk)
828{
829
830	switch (event) {
831	case MOD_LOAD:
832		if (!cold)
833			return (EPERM);
834		break;
835	case MOD_UNLOAD:
836		if (!cold && power_pm_get_type() == POWER_PM_TYPE_APM)
837			return (EBUSY);
838		break;
839	default:
840		break;
841	}
842
843	return (0);
844}
845
846/*
847 * Create "connection point"
848 */
849static void
850apm_identify(driver_t *driver, device_t parent)
851{
852	device_t child;
853
854	if (!cold) {
855		printf("Don't load this driver from userland!!\n");
856		return;
857	}
858
859	if (resource_disabled("apm", 0))
860		return;
861
862	child = BUS_ADD_CHILD(parent, 0, "apm", 0);
863	if (child == NULL)
864		panic("apm_identify");
865}
866
867/*
868 * probe for APM BIOS
869 */
870static int
871apm_probe(device_t dev)
872{
873#define APM_KERNBASE	KERNBASE
874	struct vm86frame	vmf;
875	struct apm_softc	*sc = &apm_softc;
876#ifdef PC98
877	int			rid;
878#endif
879
880	device_set_desc(dev, "APM BIOS");
881	if (device_get_unit(dev) > 0) {
882		printf("apm: Only one APM driver supported.\n");
883		return ENXIO;
884	}
885
886	if (power_pm_get_type() != POWER_PM_TYPE_NONE &&
887	    power_pm_get_type() != POWER_PM_TYPE_APM) {
888		printf("apm: Other PM system enabled.\n");
889		return ENXIO;
890	}
891
892	bzero(&vmf, sizeof(struct vm86frame));		/* safety */
893	bzero(&apm_softc, sizeof(apm_softc));
894	vmf.vmf_ah = APM_BIOS;
895	vmf.vmf_al = APM_INSTCHECK;
896	vmf.vmf_bx = 0;
897	if (vm86_intcall(APM_INT, &vmf))
898		return ENXIO;			/* APM not found */
899	if (vmf.vmf_bx != 0x504d) {
900		printf("apm: incorrect signature (0x%x)\n", vmf.vmf_bx);
901		return ENXIO;
902	}
903	if ((vmf.vmf_cx & (APM_32BIT_SUPPORT | APM_16BIT_SUPPORT)) == 0) {
904		printf("apm: protected mode connections are not supported\n");
905		return ENXIO;
906	}
907
908	apm_version = vmf.vmf_ax;
909	sc->slow_idle_cpu = ((vmf.vmf_cx & APM_CPUIDLE_SLOW) != 0);
910	sc->disabled = ((vmf.vmf_cx & APM_DISABLED) != 0);
911	sc->disengaged = ((vmf.vmf_cx & APM_DISENGAGED) != 0);
912
913	vmf.vmf_ah = APM_BIOS;
914	vmf.vmf_al = APM_DISCONNECT;
915	vmf.vmf_bx = 0;
916        vm86_intcall(APM_INT, &vmf);		/* disconnect, just in case */
917
918#ifdef PC98
919	/* PC98 have bogos APM 32bit BIOS */
920	if ((vmf.vmf_cx & APM_32BIT_SUPPORT) == 0)
921		return ENXIO;
922	rid = 0;
923	bus_set_resource(dev, SYS_RES_IOPORT, rid,
924			 APM_NECSMM_PORT, APM_NECSMM_PORTSZ);
925	sc->sc_res = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid,
926			 APM_NECSMM_PORT, ~0, APM_NECSMM_PORTSZ, RF_ACTIVE);
927	if (sc->sc_res == NULL) {
928		printf("apm: cannot open NEC smm device\n");
929		return ENXIO;
930	}
931	bus_release_resource(dev, SYS_RES_IOPORT, rid, sc->sc_res);
932
933	vmf.vmf_ah = APM_BIOS;
934	vmf.vmf_al = APM_PROT32CONNECT;
935	vmf.vmf_bx = 0;
936	if (vm86_intcall(APM_INT, &vmf)) {
937		printf("apm: 32-bit connection error.\n");
938		return (ENXIO);
939 	}
940
941	sc->bios.seg.code32.base = (vmf.vmf_ax << 4) + APM_KERNBASE;
942	sc->bios.seg.code32.limit = 0xffff;
943	sc->bios.seg.code16.base = (vmf.vmf_cx << 4) + APM_KERNBASE;
944	sc->bios.seg.code16.limit = 0xffff;
945	sc->bios.seg.data.base = (vmf.vmf_dx << 4) + APM_KERNBASE;
946	sc->bios.seg.data.limit = 0xffff;
947	sc->bios.entry = vmf.vmf_ebx;
948	sc->connectmode = APM_PROT32CONNECT;
949#else
950	if ((vmf.vmf_cx & APM_32BIT_SUPPORT) != 0) {
951		vmf.vmf_ah = APM_BIOS;
952		vmf.vmf_al = APM_PROT32CONNECT;
953		vmf.vmf_bx = 0;
954		if (vm86_intcall(APM_INT, &vmf)) {
955			printf("apm: 32-bit connection error.\n");
956			return (ENXIO);
957 		}
958		sc->bios.seg.code32.base = (vmf.vmf_ax << 4) + APM_KERNBASE;
959		sc->bios.seg.code32.limit = 0xffff;
960		sc->bios.seg.code16.base = (vmf.vmf_cx << 4) + APM_KERNBASE;
961		sc->bios.seg.code16.limit = 0xffff;
962		sc->bios.seg.data.base = (vmf.vmf_dx << 4) + APM_KERNBASE;
963		sc->bios.seg.data.limit = 0xffff;
964		sc->bios.entry = vmf.vmf_ebx;
965		sc->connectmode = APM_PROT32CONNECT;
966 	} else {
967		/* use 16-bit connection */
968		vmf.vmf_ah = APM_BIOS;
969		vmf.vmf_al = APM_PROT16CONNECT;
970		vmf.vmf_bx = 0;
971		if (vm86_intcall(APM_INT, &vmf)) {
972			printf("apm: 16-bit connection error.\n");
973			return (ENXIO);
974		}
975		sc->bios.seg.code16.base = (vmf.vmf_ax << 4) + APM_KERNBASE;
976		sc->bios.seg.code16.limit = 0xffff;
977		sc->bios.seg.data.base = (vmf.vmf_cx << 4) + APM_KERNBASE;
978		sc->bios.seg.data.limit = 0xffff;
979		sc->bios.entry = vmf.vmf_bx;
980		sc->connectmode = APM_PROT16CONNECT;
981	}
982#endif
983	return(0);
984}
985
986
987/*
988 * return 0 if the user will notice and handle the event,
989 * return 1 if the kernel driver should do so.
990 */
991static int
992apm_record_event(struct apm_softc *sc, u_int event_type)
993{
994	struct apm_event_info *evp;
995
996	if ((sc->sc_flags & SCFLAG_OPEN) == 0)
997		return 1;		/* no user waiting */
998	if (sc->event_count == APM_NEVENTS)
999		return 1;			/* overflow */
1000	if (sc->event_filter[event_type] == 0)
1001		return 1;		/* not registered */
1002	evp = &sc->event_list[sc->event_ptr];
1003	sc->event_count++;
1004	sc->event_ptr++;
1005	sc->event_ptr %= APM_NEVENTS;
1006	evp->type = event_type;
1007	evp->index = ++apm_evindex;
1008	selwakeuppri(&sc->sc_rsel, PZERO);
1009	return (sc->sc_flags & SCFLAG_OCTL) ? 0 : 1; /* user may handle */
1010}
1011
1012/* Power profile */
1013static void
1014apm_power_profile(struct apm_softc *sc)
1015{
1016	int state;
1017	struct apm_info info;
1018	static int apm_acline = 0;
1019
1020	if (apm_get_info(&info))
1021		return;
1022
1023	if (apm_acline != info.ai_acline) {
1024		apm_acline = info.ai_acline;
1025		state = apm_acline ? POWER_PROFILE_PERFORMANCE : POWER_PROFILE_ECONOMY;
1026		power_profile_set_state(state);
1027	}
1028}
1029
1030/* Process APM event */
1031static void
1032apm_processevent(void)
1033{
1034	int apm_event;
1035	struct apm_softc *sc = &apm_softc;
1036
1037#define OPMEV_DEBUGMESSAGE(symbol) case symbol:				\
1038	APM_DPRINT("Received APM Event: " #symbol "\n");
1039
1040	do {
1041		apm_event = apm_getevent();
1042		switch (apm_event) {
1043		    OPMEV_DEBUGMESSAGE(PMEV_STANDBYREQ);
1044			if (apm_op_inprog == 0) {
1045			    apm_op_inprog++;
1046			    if (apm_record_event(sc, apm_event)) {
1047				apm_suspend(PMST_STANDBY);
1048			    }
1049			}
1050			break;
1051		    OPMEV_DEBUGMESSAGE(PMEV_USERSTANDBYREQ);
1052			if (apm_op_inprog == 0) {
1053			    apm_op_inprog++;
1054			    if (apm_record_event(sc, apm_event)) {
1055				apm_suspend(PMST_STANDBY);
1056			    }
1057			}
1058			break;
1059		    OPMEV_DEBUGMESSAGE(PMEV_SUSPENDREQ);
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_USERSUSPENDREQ);
1069 			apm_lastreq_notify();
1070			if (apm_op_inprog == 0) {
1071			    apm_op_inprog++;
1072			    if (apm_record_event(sc, apm_event)) {
1073				apm_do_suspend();
1074			    }
1075			}
1076			return; /* XXX skip the rest */
1077		    OPMEV_DEBUGMESSAGE(PMEV_CRITSUSPEND);
1078			apm_do_suspend();
1079			break;
1080		    OPMEV_DEBUGMESSAGE(PMEV_NORMRESUME);
1081			apm_record_event(sc, apm_event);
1082			apm_resume();
1083			break;
1084		    OPMEV_DEBUGMESSAGE(PMEV_CRITRESUME);
1085			apm_record_event(sc, apm_event);
1086			apm_resume();
1087			break;
1088		    OPMEV_DEBUGMESSAGE(PMEV_STANDBYRESUME);
1089			apm_record_event(sc, apm_event);
1090			break;
1091		    OPMEV_DEBUGMESSAGE(PMEV_BATTERYLOW);
1092			if (apm_record_event(sc, apm_event)) {
1093			    apm_battery_low();
1094			    apm_suspend(PMST_SUSPEND);
1095			}
1096			break;
1097		    OPMEV_DEBUGMESSAGE(PMEV_POWERSTATECHANGE);
1098			apm_record_event(sc, apm_event);
1099			apm_power_profile(sc);
1100			break;
1101		    OPMEV_DEBUGMESSAGE(PMEV_UPDATETIME);
1102			apm_record_event(sc, apm_event);
1103			inittodr(0);	/* adjust time to RTC */
1104			break;
1105		    OPMEV_DEBUGMESSAGE(PMEV_CAPABILITIESCHANGE);
1106			apm_record_event(sc, apm_event);
1107			apm_power_profile(sc);
1108			break;
1109		    case PMEV_NOEVENT:
1110			break;
1111		    default:
1112			printf("Unknown Original APM Event 0x%x\n", apm_event);
1113			    break;
1114		}
1115	} while (apm_event != PMEV_NOEVENT);
1116#ifdef PC98
1117	apm_disable_smm(sc);
1118#endif
1119}
1120
1121/*
1122 * Attach APM:
1123 *
1124 * Initialize APM driver
1125 */
1126
1127static int
1128apm_attach(device_t dev)
1129{
1130	struct apm_softc	*sc = &apm_softc;
1131	int			drv_version;
1132#ifdef PC98
1133	int			rid;
1134#endif
1135
1136	if (device_get_flags(dev) & 0x20)
1137		statclock_disable = 1;
1138
1139	sc->initialized = 0;
1140
1141	/* Must be externally enabled */
1142	sc->active = 0;
1143
1144	/* Always call HLT in idle loop */
1145	sc->always_halt_cpu = 1;
1146
1147	getenv_int("debug.apm_debug", &apm_debug);
1148
1149	/* print bootstrap messages */
1150	APM_DPRINT("apm: APM BIOS version %04lx\n",  apm_version);
1151	APM_DPRINT("apm: Code16 0x%08x, Data 0x%08x\n",
1152	    sc->bios.seg.code16.base, sc->bios.seg.data.base);
1153	APM_DPRINT("apm: Code entry 0x%08x, Idling CPU %s, Management %s\n",
1154	    sc->bios.entry, is_enabled(sc->slow_idle_cpu),
1155	    is_enabled(!sc->disabled));
1156	APM_DPRINT("apm: CS_limit=0x%x, DS_limit=0x%x\n",
1157	    sc->bios.seg.code16.limit, sc->bios.seg.data.limit);
1158
1159#ifdef PC98
1160	rid = 0;
1161	sc->sc_res = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid,
1162			 APM_NECSMM_PORT, ~0, APM_NECSMM_PORTSZ, RF_ACTIVE);
1163	if (sc->sc_res == NULL)
1164		panic("%s: counldn't map I/O ports", device_get_name(dev));
1165	sc->sc_iot = rman_get_bustag(sc->sc_res);
1166	sc->sc_ioh = rman_get_bushandle(sc->sc_res);
1167
1168	if (apm_version==0x112 || apm_version==0x111 || apm_version==0x110)
1169		apm_necsmm_addr = APM_NECSMM_PORT;
1170	else
1171		apm_necsmm_addr = 0;
1172	apm_necsmm_mask = ~APM_NECSMM_EN;
1173#endif /* PC98 */
1174
1175	/*
1176         * In one test, apm bios version was 1.02; an attempt to register
1177         * a 1.04 driver resulted in a 1.00 connection!  Registering a
1178         * 1.02 driver resulted in a 1.02 connection.
1179         */
1180	drv_version = apm_version > 0x102 ? 0x102 : apm_version;
1181	for (; drv_version > 0x100; drv_version--)
1182		if (apm_driver_version(drv_version) == 0)
1183			break;
1184	sc->minorversion = ((drv_version & 0x00f0) >>  4) * 10 +
1185		((drv_version & 0x000f) >> 0);
1186	sc->majorversion = ((drv_version & 0xf000) >> 12) * 10 +
1187		((apm_version & 0x0f00) >> 8);
1188
1189	sc->intversion = INTVERSION(sc->majorversion, sc->minorversion);
1190
1191	if (sc->intversion >= INTVERSION(1, 1))
1192		APM_DPRINT("apm: Engaged control %s\n", is_enabled(!sc->disengaged));
1193	device_printf(dev, "found APM BIOS v%ld.%ld, connected at v%d.%d\n",
1194	       ((apm_version & 0xf000) >> 12) * 10 + ((apm_version & 0x0f00) >> 8),
1195	       ((apm_version & 0x00f0) >> 4) * 10 + ((apm_version & 0x000f) >> 0),
1196	       sc->majorversion, sc->minorversion);
1197
1198
1199	APM_DPRINT("apm: Slow Idling CPU %s\n", is_enabled(sc->slow_idle_cpu));
1200	/* enable power management */
1201	if (sc->disabled) {
1202		if (apm_enable_disable_pm(1)) {
1203			APM_DPRINT("apm: *Warning* enable function failed! [%x]\n",
1204			    (sc->bios.r.eax >> 8) & 0xff);
1205		}
1206	}
1207
1208	/* engage power managment (APM 1.1 or later) */
1209	if (sc->intversion >= INTVERSION(1, 1) && sc->disengaged) {
1210		if (apm_engage_disengage_pm(1)) {
1211			APM_DPRINT("apm: *Warning* engage function failed err=[%x]",
1212			    (sc->bios.r.eax >> 8) & 0xff);
1213			APM_DPRINT(" (Docked or using external power?).\n");
1214		}
1215	}
1216
1217	/* Power the system off using APM */
1218	EVENTHANDLER_REGISTER(shutdown_final, apm_power_off, NULL,
1219			      SHUTDOWN_PRI_LAST);
1220
1221	/* Register APM again to pass the correct argument of pm_func. */
1222	power_pm_register(POWER_PM_TYPE_APM, apm_pm_func, sc);
1223
1224	sc->initialized = 1;
1225	sc->suspending = 0;
1226
1227	make_dev(&apm_cdevsw, 0, 0, 5, 0664, "apm");
1228	make_dev(&apm_cdevsw, 8, 0, 5, 0660, "apmctl");
1229	return 0;
1230}
1231
1232static int
1233apmopen(struct cdev *dev, int flag, int fmt, struct thread *td)
1234{
1235	struct apm_softc *sc = &apm_softc;
1236	int ctl = APMDEV(dev);
1237
1238	if (sc == NULL || sc->initialized == 0)
1239		return (ENXIO);
1240
1241	switch (ctl) {
1242	case APMDEV_CTL:
1243		if (!(flag & FWRITE))
1244			return EINVAL;
1245		if (sc->sc_flags & SCFLAG_OCTL)
1246			return EBUSY;
1247		sc->sc_flags |= SCFLAG_OCTL;
1248		bzero(sc->event_filter, sizeof sc->event_filter);
1249		break;
1250	case APMDEV_NORMAL:
1251		sc->sc_flags |= SCFLAG_ONORMAL;
1252		break;
1253	default:
1254		return ENXIO;
1255		break;
1256	}
1257	return 0;
1258}
1259
1260static int
1261apmclose(struct cdev *dev, int flag, int fmt, struct thread *td)
1262{
1263	struct apm_softc *sc = &apm_softc;
1264	int ctl = APMDEV(dev);
1265
1266	switch (ctl) {
1267	case APMDEV_CTL:
1268		apm_lastreq_rejected();
1269		sc->sc_flags &= ~SCFLAG_OCTL;
1270		bzero(sc->event_filter, sizeof sc->event_filter);
1271		break;
1272	case APMDEV_NORMAL:
1273		sc->sc_flags &= ~SCFLAG_ONORMAL;
1274		break;
1275	}
1276	if ((sc->sc_flags & SCFLAG_OPEN) == 0) {
1277		sc->event_count = 0;
1278		sc->event_ptr = 0;
1279	}
1280	return 0;
1281}
1282
1283static int
1284apmioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, struct thread *td)
1285{
1286	struct apm_softc *sc = &apm_softc;
1287	struct apm_bios_arg *args;
1288	int error = 0;
1289	int ret;
1290	int newstate;
1291
1292	if (sc == NULL || sc->initialized == 0)
1293		return (ENXIO);
1294
1295	APM_DPRINT("APM ioctl: cmd = 0x%lx\n", cmd);
1296	switch (cmd) {
1297	case APMIO_SUSPEND:
1298		if (!(flag & FWRITE))
1299			return (EPERM);
1300		if (sc->active)
1301			apm_suspend(PMST_SUSPEND);
1302		else
1303			error = EINVAL;
1304		break;
1305
1306	case APMIO_STANDBY:
1307		if (!(flag & FWRITE))
1308			return (EPERM);
1309		if (sc->active)
1310			apm_suspend(PMST_STANDBY);
1311		else
1312			error = EINVAL;
1313		break;
1314
1315	case APMIO_GETINFO_OLD:
1316		{
1317			struct apm_info info;
1318			apm_info_old_t aiop;
1319
1320			if (apm_get_info(&info))
1321				error = ENXIO;
1322			aiop = (apm_info_old_t)addr;
1323			aiop->ai_major = info.ai_major;
1324			aiop->ai_minor = info.ai_minor;
1325			aiop->ai_acline = info.ai_acline;
1326			aiop->ai_batt_stat = info.ai_batt_stat;
1327			aiop->ai_batt_life = info.ai_batt_life;
1328			aiop->ai_status = info.ai_status;
1329		}
1330		break;
1331	case APMIO_GETINFO:
1332		if (apm_get_info((apm_info_t)addr))
1333			error = ENXIO;
1334		break;
1335	case APMIO_GETPWSTATUS:
1336		if (apm_get_pwstatus((apm_pwstatus_t)addr))
1337			error = ENXIO;
1338		break;
1339	case APMIO_ENABLE:
1340		if (!(flag & FWRITE))
1341			return (EPERM);
1342		apm_event_enable();
1343		break;
1344	case APMIO_DISABLE:
1345		if (!(flag & FWRITE))
1346			return (EPERM);
1347		apm_event_disable();
1348		break;
1349	case APMIO_HALTCPU:
1350		if (!(flag & FWRITE))
1351			return (EPERM);
1352		apm_halt_cpu();
1353		break;
1354	case APMIO_NOTHALTCPU:
1355		if (!(flag & FWRITE))
1356			return (EPERM);
1357		apm_not_halt_cpu();
1358		break;
1359	case APMIO_DISPLAY:
1360		if (!(flag & FWRITE))
1361			return (EPERM);
1362		newstate = *(int *)addr;
1363		if (apm_display(newstate))
1364			error = ENXIO;
1365		break;
1366	case APMIO_BIOS:
1367		if (!(flag & FWRITE))
1368			return (EPERM);
1369		/* XXX compatibility with the old interface */
1370		args = (struct apm_bios_arg *)addr;
1371		sc->bios.r.eax = args->eax;
1372		sc->bios.r.ebx = args->ebx;
1373		sc->bios.r.ecx = args->ecx;
1374		sc->bios.r.edx = args->edx;
1375		sc->bios.r.esi = args->esi;
1376		sc->bios.r.edi = args->edi;
1377		if ((ret = apm_bioscall())) {
1378			/*
1379			 * Return code 1 means bios call was unsuccessful.
1380			 * Error code is stored in %ah.
1381			 * Return code -1 means bios call was unsupported
1382			 * in the APM BIOS version.
1383			 */
1384			if (ret == -1) {
1385				error = EINVAL;
1386			}
1387		} else {
1388			/*
1389			 * Return code 0 means bios call was successful.
1390			 * We need only %al and can discard %ah.
1391			 */
1392			sc->bios.r.eax &= 0xff;
1393		}
1394		args->eax = sc->bios.r.eax;
1395		args->ebx = sc->bios.r.ebx;
1396		args->ecx = sc->bios.r.ecx;
1397		args->edx = sc->bios.r.edx;
1398		args->esi = sc->bios.r.esi;
1399		args->edi = sc->bios.r.edi;
1400		break;
1401	default:
1402		error = EINVAL;
1403		break;
1404	}
1405
1406	/* for /dev/apmctl */
1407	if (APMDEV(dev) == APMDEV_CTL) {
1408		struct apm_event_info *evp;
1409		int i;
1410
1411		error = 0;
1412		switch (cmd) {
1413		case APMIO_NEXTEVENT:
1414			if (!sc->event_count) {
1415				error = EAGAIN;
1416			} else {
1417				evp = (struct apm_event_info *)addr;
1418				i = sc->event_ptr + APM_NEVENTS - sc->event_count;
1419				i %= APM_NEVENTS;
1420				*evp = sc->event_list[i];
1421				sc->event_count--;
1422			}
1423			break;
1424		case APMIO_REJECTLASTREQ:
1425			if (apm_lastreq_rejected()) {
1426				error = EINVAL;
1427			}
1428			break;
1429		default:
1430			error = EINVAL;
1431			break;
1432		}
1433	}
1434
1435	return error;
1436}
1437
1438static int
1439apmwrite(struct cdev *dev, struct uio *uio, int ioflag)
1440{
1441	struct apm_softc *sc = &apm_softc;
1442	u_int event_type;
1443	int error;
1444	u_char enabled;
1445
1446	if (APMDEV(dev) != APMDEV_CTL)
1447		return(ENODEV);
1448	if (uio->uio_resid != sizeof(u_int))
1449		return(E2BIG);
1450
1451	if ((error = uiomove((caddr_t)&event_type, sizeof(u_int), uio)))
1452		return(error);
1453
1454	if (event_type < 0 || event_type >= APM_NPMEV)
1455		return(EINVAL);
1456
1457	if (sc->event_filter[event_type] == 0) {
1458		enabled = 1;
1459	} else {
1460		enabled = 0;
1461	}
1462	sc->event_filter[event_type] = enabled;
1463	APM_DPRINT("apmwrite: event 0x%x %s\n", event_type, is_enabled(enabled));
1464
1465	return uio->uio_resid;
1466}
1467
1468static int
1469apmpoll(struct cdev *dev, int events, struct thread *td)
1470{
1471	struct apm_softc *sc = &apm_softc;
1472	int revents = 0;
1473
1474	if (events & (POLLIN | POLLRDNORM)) {
1475		if (sc->event_count) {
1476			revents |= events & (POLLIN | POLLRDNORM);
1477		} else {
1478			selrecord(td, &sc->sc_rsel);
1479		}
1480	}
1481
1482	return (revents);
1483}
1484
1485static device_method_t apm_methods[] = {
1486	/* Device interface */
1487	DEVMETHOD(device_identify,	apm_identify),
1488	DEVMETHOD(device_probe,		apm_probe),
1489	DEVMETHOD(device_attach,	apm_attach),
1490
1491	{ 0, 0 }
1492};
1493
1494static driver_t apm_driver = {
1495	"apm",
1496	apm_methods,
1497	1,			/* no softc (XXX) */
1498};
1499
1500static devclass_t apm_devclass;
1501
1502DRIVER_MODULE(apm, legacy, apm_driver, apm_devclass, apm_modevent, 0);
1503MODULE_VERSION(apm, 1);
1504
1505static int
1506apm_pm_func(u_long cmd, void *arg, ...)
1507{
1508	int	state, apm_state;
1509	int	error;
1510	va_list	ap;
1511
1512	error = 0;
1513	switch (cmd) {
1514	case POWER_CMD_SUSPEND:
1515		va_start(ap, arg);
1516		state = va_arg(ap, int);
1517		va_end(ap);
1518
1519		switch (state) {
1520		case POWER_SLEEP_STATE_STANDBY:
1521			apm_state = PMST_STANDBY;
1522			break;
1523		case POWER_SLEEP_STATE_SUSPEND:
1524		case POWER_SLEEP_STATE_HIBERNATE:
1525			apm_state = PMST_SUSPEND;
1526			break;
1527		default:
1528			error = EINVAL;
1529			goto out;
1530		}
1531
1532		apm_suspend(apm_state);
1533		break;
1534
1535	default:
1536		error = EINVAL;
1537		goto out;
1538	}
1539
1540out:
1541	return (error);
1542}
1543
1544static void
1545apm_pm_register(void *arg)
1546{
1547
1548	if (!resource_disabled("apm", 0))
1549		power_pm_register(POWER_PM_TYPE_APM, apm_pm_func, NULL);
1550}
1551
1552SYSINIT(power, SI_SUB_KLD, SI_ORDER_ANY, apm_pm_register, 0);
1553