apm.c revision 158922
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 158922 2006-05-25 23:06:38Z imp $");
21
22#include <sys/param.h>
23#include <sys/systm.h>
24#include <sys/bus.h>
25#include <sys/conf.h>
26#include <sys/condvar.h>
27#include <sys/eventhandler.h>
28#include <sys/fcntl.h>
29#include <sys/kernel.h>
30#include <sys/kthread.h>
31#include <sys/lock.h>
32#include <sys/module.h>
33#include <sys/mutex.h>
34#include <sys/poll.h>
35#include <sys/power.h>
36#include <sys/reboot.h>
37#include <sys/selinfo.h>
38#include <sys/signalvar.h>
39#include <sys/sysctl.h>
40#include <sys/syslog.h>
41#include <sys/time.h>
42#include <sys/uio.h>
43
44#include <machine/apm_bios.h>
45#include <machine/clock.h>
46#include <machine/endian.h>
47#include <machine/pc/bios.h>
48#include <machine/cpufunc.h>
49#include <machine/segments.h>
50#include <machine/stdarg.h>
51#include <machine/vm86.h>
52
53#include <machine/bus.h>
54#include <machine/resource.h>
55#include <sys/rman.h>
56
57#include <vm/vm.h>
58#include <vm/pmap.h>
59#include <vm/vm_param.h>
60
61#include <i386/bios/apm.h>
62
63/* Used by the apm_saver screen saver module */
64int apm_display(int newstate);
65struct apm_softc apm_softc;
66
67static void apm_resume(void);
68static int apm_bioscall(void);
69static int apm_check_function_supported(u_int version, u_int func);
70
71static int apm_pm_func(u_long, void*, ...);
72
73static u_long	apm_version;
74
75int	apm_evindex;
76
77#define	SCFLAG_ONORMAL	0x0000001
78#define	SCFLAG_OCTL	0x0000002
79#define	SCFLAG_OPEN	(SCFLAG_ONORMAL|SCFLAG_OCTL)
80
81#define APMDEV(dev)	(minor(dev)&0x0f)
82#define APMDEV_NORMAL	0
83#define APMDEV_CTL	8
84
85#ifdef PC98
86extern int bios32_apm98(struct bios_regs *, u_int, u_short);
87
88/* PC98's SMM definition */
89#define	APM_NECSMM_PORT		0x6b8e
90#define	APM_NECSMM_PORTSZ	1
91#define	APM_NECSMM_EN		0x10
92static __inline void apm_enable_smm(struct apm_softc *);
93static __inline void apm_disable_smm(struct apm_softc *);
94int apm_necsmm_addr;
95u_int32_t apm_necsmm_mask;
96#endif
97
98static struct apmhook	*hook[NAPM_HOOK];		/* XXX */
99
100#define is_enabled(foo) ((foo) ? "enabled" : "disabled")
101
102/* Map version number to integer (keeps ordering of version numbers) */
103#define INTVERSION(major, minor)	((major)*100 + (minor))
104
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 thread loop.
732 *
733 * This routine wakes up from time to time to deal with delaying the
734 * suspend of the system, or other events.
735 */
736static void
737apm_event_thread(void *arg)
738{
739	struct apm_softc *sc = &apm_softc;
740
741	sc->running = 1;
742	while (sc->active) {
743		if (apm_op_inprog)
744			apm_lastreq_notify();
745		if (sc->standbys && sc->standby_countdown-- <= 0)
746			apm_do_standby();
747		if (sc->suspends && sc->suspend_countdown-- <= 0)
748			apm_do_suspend();
749		if (!sc->bios_busy)
750			apm_processevent();
751		mtx_lock(&sc->mtx);
752		cv_timedwait(&sc->cv, &sc->mtx, 10 * hz / 9);
753		mtx_unlock(&sc->mtx);
754	}
755	sc->running = 0;
756	kthread_exit(0);
757}
758
759/* enable APM BIOS */
760static void
761apm_event_enable(void)
762{
763	struct apm_softc *sc = &apm_softc;
764
765	APM_DPRINT("called apm_event_enable()\n");
766
767	if (sc == NULL || sc->initialized == 0)
768		return;
769
770	/* Start the thread */
771	sc->active = 1;
772	if (kthread_create(apm_event_thread, sc, &sc->event_thread, 0, 0,
773	    "apm worker"))
774		panic("Cannot create apm worker thread");
775
776	return;
777}
778
779/* disable APM BIOS */
780static void
781apm_event_disable(void)
782{
783	struct apm_softc *sc = &apm_softc;
784
785	APM_DPRINT("called apm_event_disable()\n");
786
787	if (sc == NULL || sc->initialized == 0)
788		return;
789
790	mtx_lock(&sc->mtx);
791	sc->active = 0;
792	while (sc->running) {
793		cv_broadcast(&sc->cv);
794		msleep(sc->event_thread, &sc->mtx, PWAIT, "apmdie", 0);
795	}
796	mtx_unlock(&sc->mtx);
797	sc->event_thread = NULL;
798	return;
799}
800
801/* halt CPU in scheduling loop */
802static void
803apm_halt_cpu(void)
804{
805	struct apm_softc *sc = &apm_softc;
806
807	if (sc == NULL || sc->initialized == 0)
808		return;
809
810	sc->always_halt_cpu = 1;
811
812	return;
813}
814
815/* don't halt CPU in scheduling loop */
816static void
817apm_not_halt_cpu(void)
818{
819	struct apm_softc *sc = &apm_softc;
820
821	if (sc == NULL || sc->initialized == 0)
822		return;
823
824	sc->always_halt_cpu = 0;
825
826	return;
827}
828
829/* device driver definitions */
830
831/*
832 * Module event
833 */
834
835static int
836apm_modevent(struct module *mod, int event, void *junk)
837{
838
839	switch (event) {
840	case MOD_LOAD:
841		if (!cold)
842			return (EPERM);
843		break;
844	case MOD_UNLOAD:
845		if (!cold && power_pm_get_type() == POWER_PM_TYPE_APM)
846			return (EBUSY);
847		break;
848	default:
849		break;
850	}
851
852	return (0);
853}
854
855/*
856 * Create "connection point"
857 */
858static void
859apm_identify(driver_t *driver, device_t parent)
860{
861	device_t child;
862
863	if (!cold) {
864		printf("Don't load this driver from userland!!\n");
865		return;
866	}
867
868	if (resource_disabled("apm", 0))
869		return;
870
871	child = BUS_ADD_CHILD(parent, 0, "apm", 0);
872	if (child == NULL)
873		panic("apm_identify");
874}
875
876/*
877 * probe for APM BIOS
878 */
879static int
880apm_probe(device_t dev)
881{
882#define APM_KERNBASE	KERNBASE
883	struct vm86frame	vmf;
884	struct apm_softc	*sc = &apm_softc;
885#ifdef PC98
886	int			rid;
887#endif
888
889	device_set_desc(dev, "APM BIOS");
890	if (device_get_unit(dev) > 0) {
891		printf("apm: Only one APM driver supported.\n");
892		return ENXIO;
893	}
894
895	if (power_pm_get_type() != POWER_PM_TYPE_NONE &&
896	    power_pm_get_type() != POWER_PM_TYPE_APM) {
897		printf("apm: Other PM system enabled.\n");
898		return ENXIO;
899	}
900
901	bzero(&vmf, sizeof(struct vm86frame));		/* safety */
902	bzero(&apm_softc, sizeof(apm_softc));
903	vmf.vmf_ah = APM_BIOS;
904	vmf.vmf_al = APM_INSTCHECK;
905	vmf.vmf_bx = 0;
906	if (vm86_intcall(APM_INT, &vmf))
907		return ENXIO;			/* APM not found */
908	if (vmf.vmf_bx != 0x504d) {
909		printf("apm: incorrect signature (0x%x)\n", vmf.vmf_bx);
910		return ENXIO;
911	}
912	if ((vmf.vmf_cx & (APM_32BIT_SUPPORT | APM_16BIT_SUPPORT)) == 0) {
913		printf("apm: protected mode connections are not supported\n");
914		return ENXIO;
915	}
916
917	apm_version = vmf.vmf_ax;
918	sc->slow_idle_cpu = ((vmf.vmf_cx & APM_CPUIDLE_SLOW) != 0);
919	sc->disabled = ((vmf.vmf_cx & APM_DISABLED) != 0);
920	sc->disengaged = ((vmf.vmf_cx & APM_DISENGAGED) != 0);
921
922	vmf.vmf_ah = APM_BIOS;
923	vmf.vmf_al = APM_DISCONNECT;
924	vmf.vmf_bx = 0;
925        vm86_intcall(APM_INT, &vmf);		/* disconnect, just in case */
926
927#ifdef PC98
928	/* PC98 have bogos APM 32bit BIOS */
929	if ((vmf.vmf_cx & APM_32BIT_SUPPORT) == 0)
930		return ENXIO;
931	rid = 0;
932	bus_set_resource(dev, SYS_RES_IOPORT, rid,
933			 APM_NECSMM_PORT, APM_NECSMM_PORTSZ);
934	sc->sc_res = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid,
935			 APM_NECSMM_PORT, ~0, APM_NECSMM_PORTSZ, RF_ACTIVE);
936	if (sc->sc_res == NULL) {
937		printf("apm: cannot open NEC smm device\n");
938		return ENXIO;
939	}
940	bus_release_resource(dev, SYS_RES_IOPORT, rid, sc->sc_res);
941
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
950	sc->bios.seg.code32.base = (vmf.vmf_ax << 4) + APM_KERNBASE;
951	sc->bios.seg.code32.limit = 0xffff;
952	sc->bios.seg.code16.base = (vmf.vmf_cx << 4) + APM_KERNBASE;
953	sc->bios.seg.code16.limit = 0xffff;
954	sc->bios.seg.data.base = (vmf.vmf_dx << 4) + APM_KERNBASE;
955	sc->bios.seg.data.limit = 0xffff;
956	sc->bios.entry = vmf.vmf_ebx;
957	sc->connectmode = APM_PROT32CONNECT;
958#else
959	if ((vmf.vmf_cx & APM_32BIT_SUPPORT) != 0) {
960		vmf.vmf_ah = APM_BIOS;
961		vmf.vmf_al = APM_PROT32CONNECT;
962		vmf.vmf_bx = 0;
963		if (vm86_intcall(APM_INT, &vmf)) {
964			printf("apm: 32-bit connection error.\n");
965			return (ENXIO);
966 		}
967		sc->bios.seg.code32.base = (vmf.vmf_ax << 4) + APM_KERNBASE;
968		sc->bios.seg.code32.limit = 0xffff;
969		sc->bios.seg.code16.base = (vmf.vmf_cx << 4) + APM_KERNBASE;
970		sc->bios.seg.code16.limit = 0xffff;
971		sc->bios.seg.data.base = (vmf.vmf_dx << 4) + APM_KERNBASE;
972		sc->bios.seg.data.limit = 0xffff;
973		sc->bios.entry = vmf.vmf_ebx;
974		sc->connectmode = APM_PROT32CONNECT;
975 	} else {
976		/* use 16-bit connection */
977		vmf.vmf_ah = APM_BIOS;
978		vmf.vmf_al = APM_PROT16CONNECT;
979		vmf.vmf_bx = 0;
980		if (vm86_intcall(APM_INT, &vmf)) {
981			printf("apm: 16-bit connection error.\n");
982			return (ENXIO);
983		}
984		sc->bios.seg.code16.base = (vmf.vmf_ax << 4) + APM_KERNBASE;
985		sc->bios.seg.code16.limit = 0xffff;
986		sc->bios.seg.data.base = (vmf.vmf_cx << 4) + APM_KERNBASE;
987		sc->bios.seg.data.limit = 0xffff;
988		sc->bios.entry = vmf.vmf_bx;
989		sc->connectmode = APM_PROT16CONNECT;
990	}
991#endif
992	return(0);
993}
994
995
996/*
997 * return 0 if the user will notice and handle the event,
998 * return 1 if the kernel driver should do so.
999 */
1000static int
1001apm_record_event(struct apm_softc *sc, u_int event_type)
1002{
1003	struct apm_event_info *evp;
1004
1005	if ((sc->sc_flags & SCFLAG_OPEN) == 0)
1006		return 1;		/* no user waiting */
1007	if (sc->event_count == APM_NEVENTS)
1008		return 1;			/* overflow */
1009	if (sc->event_filter[event_type] == 0)
1010		return 1;		/* not registered */
1011	evp = &sc->event_list[sc->event_ptr];
1012	sc->event_count++;
1013	sc->event_ptr++;
1014	sc->event_ptr %= APM_NEVENTS;
1015	evp->type = event_type;
1016	evp->index = ++apm_evindex;
1017	selwakeuppri(&sc->sc_rsel, PZERO);
1018	return (sc->sc_flags & SCFLAG_OCTL) ? 0 : 1; /* user may handle */
1019}
1020
1021/* Power profile */
1022static void
1023apm_power_profile(struct apm_softc *sc)
1024{
1025	int state;
1026	struct apm_info info;
1027	static int apm_acline = 0;
1028
1029	if (apm_get_info(&info))
1030		return;
1031
1032	if (apm_acline != info.ai_acline) {
1033		apm_acline = info.ai_acline;
1034		state = apm_acline ? POWER_PROFILE_PERFORMANCE : POWER_PROFILE_ECONOMY;
1035		power_profile_set_state(state);
1036	}
1037}
1038
1039/* Process APM event */
1040static void
1041apm_processevent(void)
1042{
1043	int apm_event;
1044	struct apm_softc *sc = &apm_softc;
1045
1046#define OPMEV_DEBUGMESSAGE(symbol) case symbol:				\
1047	APM_DPRINT("Received APM Event: " #symbol "\n");
1048
1049	do {
1050		apm_event = apm_getevent();
1051		switch (apm_event) {
1052		    OPMEV_DEBUGMESSAGE(PMEV_STANDBYREQ);
1053			if (apm_op_inprog == 0) {
1054			    apm_op_inprog++;
1055			    if (apm_record_event(sc, apm_event)) {
1056				apm_suspend(PMST_STANDBY);
1057			    }
1058			}
1059			break;
1060		    OPMEV_DEBUGMESSAGE(PMEV_USERSTANDBYREQ);
1061			if (apm_op_inprog == 0) {
1062			    apm_op_inprog++;
1063			    if (apm_record_event(sc, apm_event)) {
1064				apm_suspend(PMST_STANDBY);
1065			    }
1066			}
1067			break;
1068		    OPMEV_DEBUGMESSAGE(PMEV_SUSPENDREQ);
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_USERSUSPENDREQ);
1078 			apm_lastreq_notify();
1079			if (apm_op_inprog == 0) {
1080			    apm_op_inprog++;
1081			    if (apm_record_event(sc, apm_event)) {
1082				apm_do_suspend();
1083			    }
1084			}
1085			return; /* XXX skip the rest */
1086		    OPMEV_DEBUGMESSAGE(PMEV_CRITSUSPEND);
1087			apm_do_suspend();
1088			break;
1089		    OPMEV_DEBUGMESSAGE(PMEV_NORMRESUME);
1090			apm_record_event(sc, apm_event);
1091			apm_resume();
1092			break;
1093		    OPMEV_DEBUGMESSAGE(PMEV_CRITRESUME);
1094			apm_record_event(sc, apm_event);
1095			apm_resume();
1096			break;
1097		    OPMEV_DEBUGMESSAGE(PMEV_STANDBYRESUME);
1098			apm_record_event(sc, apm_event);
1099			break;
1100		    OPMEV_DEBUGMESSAGE(PMEV_BATTERYLOW);
1101			if (apm_record_event(sc, apm_event)) {
1102			    apm_battery_low();
1103			    apm_suspend(PMST_SUSPEND);
1104			}
1105			break;
1106		    OPMEV_DEBUGMESSAGE(PMEV_POWERSTATECHANGE);
1107			apm_record_event(sc, apm_event);
1108			apm_power_profile(sc);
1109			break;
1110		    OPMEV_DEBUGMESSAGE(PMEV_UPDATETIME);
1111			apm_record_event(sc, apm_event);
1112			inittodr(0);	/* adjust time to RTC */
1113			break;
1114		    OPMEV_DEBUGMESSAGE(PMEV_CAPABILITIESCHANGE);
1115			apm_record_event(sc, apm_event);
1116			apm_power_profile(sc);
1117			break;
1118		    case PMEV_NOEVENT:
1119			break;
1120		    default:
1121			printf("Unknown Original APM Event 0x%x\n", apm_event);
1122			    break;
1123		}
1124	} while (apm_event != PMEV_NOEVENT);
1125#ifdef PC98
1126	apm_disable_smm(sc);
1127#endif
1128}
1129
1130/*
1131 * Attach APM:
1132 *
1133 * Initialize APM driver
1134 */
1135
1136static int
1137apm_attach(device_t dev)
1138{
1139	struct apm_softc	*sc = &apm_softc;
1140	int			drv_version;
1141#ifdef PC98
1142	int			rid;
1143#endif
1144	mtx_init(&sc->mtx, device_get_nameunit(dev), "apm", MTX_DEF);
1145	cv_init(&sc->cv, "cbb cv");
1146
1147	if (device_get_flags(dev) & 0x20)
1148		statclock_disable = 1;
1149
1150	sc->initialized = 0;
1151
1152	/* Must be externally enabled */
1153	sc->active = 0;
1154
1155	/* Always call HLT in idle loop */
1156	sc->always_halt_cpu = 1;
1157
1158	getenv_int("debug.apm_debug", &apm_debug);
1159
1160	/* print bootstrap messages */
1161	APM_DPRINT("apm: APM BIOS version %04lx\n",  apm_version);
1162	APM_DPRINT("apm: Code16 0x%08x, Data 0x%08x\n",
1163	    sc->bios.seg.code16.base, sc->bios.seg.data.base);
1164	APM_DPRINT("apm: Code entry 0x%08x, Idling CPU %s, Management %s\n",
1165	    sc->bios.entry, is_enabled(sc->slow_idle_cpu),
1166	    is_enabled(!sc->disabled));
1167	APM_DPRINT("apm: CS_limit=0x%x, DS_limit=0x%x\n",
1168	    sc->bios.seg.code16.limit, sc->bios.seg.data.limit);
1169
1170#ifdef PC98
1171	rid = 0;
1172	sc->sc_res = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid,
1173			 APM_NECSMM_PORT, ~0, APM_NECSMM_PORTSZ, RF_ACTIVE);
1174	if (sc->sc_res == NULL)
1175		panic("%s: counldn't map I/O ports", device_get_name(dev));
1176	sc->sc_iot = rman_get_bustag(sc->sc_res);
1177	sc->sc_ioh = rman_get_bushandle(sc->sc_res);
1178
1179	if (apm_version==0x112 || apm_version==0x111 || apm_version==0x110)
1180		apm_necsmm_addr = APM_NECSMM_PORT;
1181	else
1182		apm_necsmm_addr = 0;
1183	apm_necsmm_mask = ~APM_NECSMM_EN;
1184#endif /* PC98 */
1185
1186	/*
1187         * In one test, apm bios version was 1.02; an attempt to register
1188         * a 1.04 driver resulted in a 1.00 connection!  Registering a
1189         * 1.02 driver resulted in a 1.02 connection.
1190         */
1191	drv_version = apm_version > 0x102 ? 0x102 : apm_version;
1192	for (; drv_version > 0x100; drv_version--)
1193		if (apm_driver_version(drv_version) == 0)
1194			break;
1195	sc->minorversion = ((drv_version & 0x00f0) >>  4) * 10 +
1196		((drv_version & 0x000f) >> 0);
1197	sc->majorversion = ((drv_version & 0xf000) >> 12) * 10 +
1198		((apm_version & 0x0f00) >> 8);
1199
1200	sc->intversion = INTVERSION(sc->majorversion, sc->minorversion);
1201
1202	if (sc->intversion >= INTVERSION(1, 1))
1203		APM_DPRINT("apm: Engaged control %s\n", is_enabled(!sc->disengaged));
1204	device_printf(dev, "found APM BIOS v%ld.%ld, connected at v%d.%d\n",
1205	       ((apm_version & 0xf000) >> 12) * 10 + ((apm_version & 0x0f00) >> 8),
1206	       ((apm_version & 0x00f0) >> 4) * 10 + ((apm_version & 0x000f) >> 0),
1207	       sc->majorversion, sc->minorversion);
1208
1209
1210	APM_DPRINT("apm: Slow Idling CPU %s\n", is_enabled(sc->slow_idle_cpu));
1211	/* enable power management */
1212	if (sc->disabled) {
1213		if (apm_enable_disable_pm(1)) {
1214			APM_DPRINT("apm: *Warning* enable function failed! [%x]\n",
1215			    (sc->bios.r.eax >> 8) & 0xff);
1216		}
1217	}
1218
1219	/* engage power managment (APM 1.1 or later) */
1220	if (sc->intversion >= INTVERSION(1, 1) && sc->disengaged) {
1221		if (apm_engage_disengage_pm(1)) {
1222			APM_DPRINT("apm: *Warning* engage function failed err=[%x]",
1223			    (sc->bios.r.eax >> 8) & 0xff);
1224			APM_DPRINT(" (Docked or using external power?).\n");
1225		}
1226	}
1227
1228	/* Power the system off using APM */
1229	EVENTHANDLER_REGISTER(shutdown_final, apm_power_off, NULL,
1230			      SHUTDOWN_PRI_LAST);
1231
1232	/* Register APM again to pass the correct argument of pm_func. */
1233	power_pm_register(POWER_PM_TYPE_APM, apm_pm_func, sc);
1234
1235	sc->initialized = 1;
1236	sc->suspending = 0;
1237	sc->running = 0;
1238
1239	make_dev(&apm_cdevsw, 0, 0, 5, 0664, "apm");
1240	make_dev(&apm_cdevsw, 8, 0, 5, 0660, "apmctl");
1241	return 0;
1242}
1243
1244static int
1245apmopen(struct cdev *dev, int flag, int fmt, struct thread *td)
1246{
1247	struct apm_softc *sc = &apm_softc;
1248	int ctl = APMDEV(dev);
1249
1250	if (sc == NULL || sc->initialized == 0)
1251		return (ENXIO);
1252
1253	switch (ctl) {
1254	case APMDEV_CTL:
1255		if (!(flag & FWRITE))
1256			return EINVAL;
1257		if (sc->sc_flags & SCFLAG_OCTL)
1258			return EBUSY;
1259		sc->sc_flags |= SCFLAG_OCTL;
1260		bzero(sc->event_filter, sizeof sc->event_filter);
1261		break;
1262	case APMDEV_NORMAL:
1263		sc->sc_flags |= SCFLAG_ONORMAL;
1264		break;
1265	default:
1266		return ENXIO;
1267		break;
1268	}
1269	return 0;
1270}
1271
1272static int
1273apmclose(struct cdev *dev, int flag, int fmt, struct thread *td)
1274{
1275	struct apm_softc *sc = &apm_softc;
1276	int ctl = APMDEV(dev);
1277
1278	switch (ctl) {
1279	case APMDEV_CTL:
1280		apm_lastreq_rejected();
1281		sc->sc_flags &= ~SCFLAG_OCTL;
1282		bzero(sc->event_filter, sizeof sc->event_filter);
1283		break;
1284	case APMDEV_NORMAL:
1285		sc->sc_flags &= ~SCFLAG_ONORMAL;
1286		break;
1287	}
1288	if ((sc->sc_flags & SCFLAG_OPEN) == 0) {
1289		sc->event_count = 0;
1290		sc->event_ptr = 0;
1291	}
1292	return 0;
1293}
1294
1295static int
1296apmioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, struct thread *td)
1297{
1298	struct apm_softc *sc = &apm_softc;
1299	struct apm_bios_arg *args;
1300	int error = 0;
1301	int ret;
1302	int newstate;
1303
1304	if (sc == NULL || sc->initialized == 0)
1305		return (ENXIO);
1306
1307	APM_DPRINT("APM ioctl: cmd = 0x%lx\n", cmd);
1308	switch (cmd) {
1309	case APMIO_SUSPEND:
1310		if (!(flag & FWRITE))
1311			return (EPERM);
1312		if (sc->active)
1313			apm_suspend(PMST_SUSPEND);
1314		else
1315			error = EINVAL;
1316		break;
1317
1318	case APMIO_STANDBY:
1319		if (!(flag & FWRITE))
1320			return (EPERM);
1321		if (sc->active)
1322			apm_suspend(PMST_STANDBY);
1323		else
1324			error = EINVAL;
1325		break;
1326
1327	case APMIO_GETINFO_OLD:
1328		{
1329			struct apm_info info;
1330			apm_info_old_t aiop;
1331
1332			if (apm_get_info(&info))
1333				error = ENXIO;
1334			aiop = (apm_info_old_t)addr;
1335			aiop->ai_major = info.ai_major;
1336			aiop->ai_minor = info.ai_minor;
1337			aiop->ai_acline = info.ai_acline;
1338			aiop->ai_batt_stat = info.ai_batt_stat;
1339			aiop->ai_batt_life = info.ai_batt_life;
1340			aiop->ai_status = info.ai_status;
1341		}
1342		break;
1343	case APMIO_GETINFO:
1344		if (apm_get_info((apm_info_t)addr))
1345			error = ENXIO;
1346		break;
1347	case APMIO_GETPWSTATUS:
1348		if (apm_get_pwstatus((apm_pwstatus_t)addr))
1349			error = ENXIO;
1350		break;
1351	case APMIO_ENABLE:
1352		if (!(flag & FWRITE))
1353			return (EPERM);
1354		apm_event_enable();
1355		break;
1356	case APMIO_DISABLE:
1357		if (!(flag & FWRITE))
1358			return (EPERM);
1359		apm_event_disable();
1360		break;
1361	case APMIO_HALTCPU:
1362		if (!(flag & FWRITE))
1363			return (EPERM);
1364		apm_halt_cpu();
1365		break;
1366	case APMIO_NOTHALTCPU:
1367		if (!(flag & FWRITE))
1368			return (EPERM);
1369		apm_not_halt_cpu();
1370		break;
1371	case APMIO_DISPLAY:
1372		if (!(flag & FWRITE))
1373			return (EPERM);
1374		newstate = *(int *)addr;
1375		if (apm_display(newstate))
1376			error = ENXIO;
1377		break;
1378	case APMIO_BIOS:
1379		if (!(flag & FWRITE))
1380			return (EPERM);
1381		/* XXX compatibility with the old interface */
1382		args = (struct apm_bios_arg *)addr;
1383		sc->bios.r.eax = args->eax;
1384		sc->bios.r.ebx = args->ebx;
1385		sc->bios.r.ecx = args->ecx;
1386		sc->bios.r.edx = args->edx;
1387		sc->bios.r.esi = args->esi;
1388		sc->bios.r.edi = args->edi;
1389		if ((ret = apm_bioscall())) {
1390			/*
1391			 * Return code 1 means bios call was unsuccessful.
1392			 * Error code is stored in %ah.
1393			 * Return code -1 means bios call was unsupported
1394			 * in the APM BIOS version.
1395			 */
1396			if (ret == -1) {
1397				error = EINVAL;
1398			}
1399		} else {
1400			/*
1401			 * Return code 0 means bios call was successful.
1402			 * We need only %al and can discard %ah.
1403			 */
1404			sc->bios.r.eax &= 0xff;
1405		}
1406		args->eax = sc->bios.r.eax;
1407		args->ebx = sc->bios.r.ebx;
1408		args->ecx = sc->bios.r.ecx;
1409		args->edx = sc->bios.r.edx;
1410		args->esi = sc->bios.r.esi;
1411		args->edi = sc->bios.r.edi;
1412		break;
1413	default:
1414		error = EINVAL;
1415		break;
1416	}
1417
1418	/* for /dev/apmctl */
1419	if (APMDEV(dev) == APMDEV_CTL) {
1420		struct apm_event_info *evp;
1421		int i;
1422
1423		error = 0;
1424		switch (cmd) {
1425		case APMIO_NEXTEVENT:
1426			if (!sc->event_count) {
1427				error = EAGAIN;
1428			} else {
1429				evp = (struct apm_event_info *)addr;
1430				i = sc->event_ptr + APM_NEVENTS - sc->event_count;
1431				i %= APM_NEVENTS;
1432				*evp = sc->event_list[i];
1433				sc->event_count--;
1434			}
1435			break;
1436		case APMIO_REJECTLASTREQ:
1437			if (apm_lastreq_rejected()) {
1438				error = EINVAL;
1439			}
1440			break;
1441		default:
1442			error = EINVAL;
1443			break;
1444		}
1445	}
1446
1447	return error;
1448}
1449
1450static int
1451apmwrite(struct cdev *dev, struct uio *uio, int ioflag)
1452{
1453	struct apm_softc *sc = &apm_softc;
1454	u_int event_type;
1455	int error;
1456	u_char enabled;
1457
1458	if (APMDEV(dev) != APMDEV_CTL)
1459		return(ENODEV);
1460	if (uio->uio_resid != sizeof(u_int))
1461		return(E2BIG);
1462
1463	if ((error = uiomove((caddr_t)&event_type, sizeof(u_int), uio)))
1464		return(error);
1465
1466	if (event_type < 0 || event_type >= APM_NPMEV)
1467		return(EINVAL);
1468
1469	if (sc->event_filter[event_type] == 0) {
1470		enabled = 1;
1471	} else {
1472		enabled = 0;
1473	}
1474	sc->event_filter[event_type] = enabled;
1475	APM_DPRINT("apmwrite: event 0x%x %s\n", event_type, is_enabled(enabled));
1476
1477	return uio->uio_resid;
1478}
1479
1480static int
1481apmpoll(struct cdev *dev, int events, struct thread *td)
1482{
1483	struct apm_softc *sc = &apm_softc;
1484	int revents = 0;
1485
1486	if (events & (POLLIN | POLLRDNORM)) {
1487		if (sc->event_count) {
1488			revents |= events & (POLLIN | POLLRDNORM);
1489		} else {
1490			selrecord(td, &sc->sc_rsel);
1491		}
1492	}
1493
1494	return (revents);
1495}
1496
1497static device_method_t apm_methods[] = {
1498	/* Device interface */
1499	DEVMETHOD(device_identify,	apm_identify),
1500	DEVMETHOD(device_probe,		apm_probe),
1501	DEVMETHOD(device_attach,	apm_attach),
1502
1503	{ 0, 0 }
1504};
1505
1506static driver_t apm_driver = {
1507	"apm",
1508	apm_methods,
1509	1,			/* no softc (XXX) */
1510};
1511
1512static devclass_t apm_devclass;
1513
1514DRIVER_MODULE(apm, legacy, apm_driver, apm_devclass, apm_modevent, 0);
1515MODULE_VERSION(apm, 1);
1516
1517static int
1518apm_pm_func(u_long cmd, void *arg, ...)
1519{
1520	int	state, apm_state;
1521	int	error;
1522	va_list	ap;
1523
1524	error = 0;
1525	switch (cmd) {
1526	case POWER_CMD_SUSPEND:
1527		va_start(ap, arg);
1528		state = va_arg(ap, int);
1529		va_end(ap);
1530
1531		switch (state) {
1532		case POWER_SLEEP_STATE_STANDBY:
1533			apm_state = PMST_STANDBY;
1534			break;
1535		case POWER_SLEEP_STATE_SUSPEND:
1536		case POWER_SLEEP_STATE_HIBERNATE:
1537			apm_state = PMST_SUSPEND;
1538			break;
1539		default:
1540			error = EINVAL;
1541			goto out;
1542		}
1543
1544		apm_suspend(apm_state);
1545		break;
1546
1547	default:
1548		error = EINVAL;
1549		goto out;
1550	}
1551
1552out:
1553	return (error);
1554}
1555
1556static void
1557apm_pm_register(void *arg)
1558{
1559
1560	if (!resource_disabled("apm", 0))
1561		power_pm_register(POWER_PM_TYPE_APM, apm_pm_func, NULL);
1562}
1563
1564SYSINIT(power, SI_SUB_KLD, SI_ORDER_ANY, apm_pm_register, 0);
1565