apm.c revision 49380
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 *	$Id: apm.c,v 1.97 1999/07/30 19:34:58 msmith Exp $
19 */
20
21#include "opt_devfs.h"
22
23#include <sys/param.h>
24#include <sys/systm.h>
25#include <sys/conf.h>
26#include <sys/kernel.h>
27#ifdef DEVFS
28#include <sys/devfsext.h>
29#endif /*DEVFS*/
30#include <sys/time.h>
31#include <sys/reboot.h>
32#include <sys/bus.h>
33#include <sys/select.h>
34#include <sys/poll.h>
35#include <sys/fcntl.h>
36#include <sys/proc.h>
37#include <sys/uio.h>
38#include <sys/signalvar.h>
39#include <machine/apm_bios.h>
40#include <machine/segments.h>
41#include <machine/clock.h>
42#include <vm/vm.h>
43#include <vm/vm_param.h>
44#include <vm/pmap.h>
45#include <sys/syslog.h>
46
47#include <machine/pc/bios.h>
48#include <machine/vm86.h>
49
50static int apm_display __P((int newstate));
51static void apm_resume __P((void));
52static int apm_bioscall(void);
53
54static u_long	apm_version;
55
56#define APM_NEVENTS 16
57#define APM_NPMEV   13
58
59int	apm_evindex;
60
61/* static data */
62struct apm_softc {
63	int	initialized, active, bios_busy;
64	int	always_halt_cpu, slow_idle_cpu;
65	int	disabled, disengaged;
66	u_int	minorversion, majorversion;
67	u_int	intversion, connectmode;
68	struct bios_args bios;
69	struct apmhook sc_suspend;
70	struct apmhook sc_resume;
71	struct selinfo sc_rsel;
72	int	sc_flags;
73	int	event_count;
74	int	event_ptr;
75	struct	apm_event_info event_list[APM_NEVENTS];
76	u_char	event_filter[APM_NPMEV];
77#ifdef DEVFS
78	void 	*sc_devfs_token;
79#endif
80};
81#define	SCFLAG_ONORMAL	0x0000001
82#define	SCFLAG_OCTL	0x0000002
83#define	SCFLAG_OPEN	(SCFLAG_ONORMAL|SCFLAG_OCTL)
84
85#define APMDEV(dev)	(minor(dev)&0x0f)
86#define APMDEV_NORMAL	0
87#define APMDEV_CTL	8
88
89static struct apm_softc apm_softc;
90static struct apmhook	*hook[NAPM_HOOK];		/* XXX */
91
92#define is_enabled(foo) ((foo) ? "enabled" : "disabled")
93
94/* Map version number to integer (keeps ordering of version numbers) */
95#define INTVERSION(major, minor)	((major)*100 + (minor))
96
97static struct callout_handle apm_timeout_ch =
98    CALLOUT_HANDLE_INITIALIZER(&apm_timeout_ch);
99
100static timeout_t apm_timeout;
101static d_open_t apmopen;
102static d_close_t apmclose;
103static d_write_t apmwrite;
104static d_ioctl_t apmioctl;
105static d_poll_t apmpoll;
106
107#define CDEV_MAJOR 39
108static struct cdevsw apm_cdevsw = {
109	/* open */	apmopen,
110	/* close */	apmclose,
111	/* read */	noread,
112	/* write */	apmwrite,
113	/* ioctl */	apmioctl,
114	/* stop */	nostop,
115	/* reset */	noreset,
116	/* devtotty */	nodevtotty,
117	/* poll */	apmpoll,
118	/* mmap */	nommap,
119	/* strategy */	nostrategy,
120	/* name */	"apm",
121	/* parms */	noparms,
122	/* maj */	CDEV_MAJOR,
123	/* dump */	nodump,
124	/* psize */	nopsize,
125	/* flags */	0,
126	/* maxio */	0,
127	/* bmaj */	-1
128};
129
130static int
131apm_bioscall(void)
132{
133	struct apm_softc *sc = &apm_softc;
134	int errno = 0;
135
136	sc->bios_busy = 1;
137	if (sc->connectmode == APM_PROT32CONNECT) {
138		set_bios_selectors(&sc->bios.seg,
139				   BIOSCODE_FLAG | BIOSDATA_FLAG);
140		errno = bios32(&sc->bios.r,
141			       sc->bios.entry, GSEL(GBIOSCODE32_SEL, SEL_KPL));
142	} else {
143		errno = bios16(&sc->bios, NULL);
144	}
145	sc->bios_busy = 0;
146	return (errno);
147}
148
149/* enable/disable power management */
150static int
151apm_enable_disable_pm(int enable)
152{
153	struct apm_softc *sc = &apm_softc;
154
155	sc->bios.r.eax = (APM_BIOS << 8) | APM_ENABLEDISABLEPM;
156
157	if (sc->intversion >= INTVERSION(1, 1))
158		sc->bios.r.ebx  = PMDV_ALLDEV;
159	else
160		sc->bios.r.ebx  = 0xffff;	/* APM version 1.0 only */
161	sc->bios.r.ecx  = enable;
162	sc->bios.r.edx = 0;
163	return (apm_bioscall());
164}
165
166/* register driver version (APM 1.1 or later) */
167static int
168apm_driver_version(int version)
169{
170	struct apm_softc *sc = &apm_softc;
171
172	sc->bios.r.eax = (APM_BIOS << 8) | APM_DRVVERSION;
173	sc->bios.r.ebx  = 0x0;
174	sc->bios.r.ecx  = version;
175	sc->bios.r.edx = 0;
176
177	if (apm_bioscall() == 0 && sc->bios.r.eax == version)
178		return (0);
179	return (1);
180}
181
182/* engage/disengage power management (APM 1.1 or later) */
183static int
184apm_engage_disengage_pm(int engage)
185{
186	struct apm_softc *sc = &apm_softc;
187
188	sc->bios.r.eax = (APM_BIOS << 8) | APM_ENGAGEDISENGAGEPM;
189	sc->bios.r.ebx = PMDV_ALLDEV;
190	sc->bios.r.ecx = engage;
191	sc->bios.r.edx = 0;
192	return (apm_bioscall());
193}
194
195/* get PM event */
196static u_int
197apm_getevent(void)
198{
199	struct apm_softc *sc = &apm_softc;
200
201	sc->bios.r.eax = (APM_BIOS << 8) | APM_GETPMEVENT;
202
203	sc->bios.r.ebx = 0;
204	sc->bios.r.ecx = 0;
205	sc->bios.r.edx = 0;
206	if (apm_bioscall())
207		return (PMEV_NOEVENT);
208	return (sc->bios.r.ebx & 0xffff);
209}
210
211/* suspend entire system */
212static int
213apm_suspend_system(int state)
214{
215	struct apm_softc *sc = &apm_softc;
216
217	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
218	sc->bios.r.ebx = PMDV_ALLDEV;
219	sc->bios.r.ecx = state;
220	sc->bios.r.edx = 0;
221
222	if (apm_bioscall()) {
223 		printf("Entire system suspend failure: errcode = %d\n",
224		       0xff & (sc->bios.r.eax >> 8));
225 		return 1;
226 	}
227 	return 0;
228}
229
230/* Display control */
231/*
232 * Experimental implementation: My laptop machine can't handle this function
233 * If your laptop can control the display via APM, please inform me.
234 *                            HOSOKAWA, Tatsumi <hosokawa@jp.FreeBSD.org>
235 */
236static int
237apm_display(int newstate)
238{
239	struct apm_softc *sc = &apm_softc;
240
241	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
242	sc->bios.r.ebx = PMDV_DISP0;
243	sc->bios.r.ecx = newstate ? PMST_APMENABLED:PMST_SUSPEND;
244	sc->bios.r.edx = 0;
245	if (apm_bioscall()) {
246 		printf("Display off failure: errcode = %d\n",
247		       0xff & (sc->bios.r.eax >> 8));
248 		return 1;
249 	}
250 	return 0;
251}
252
253/*
254 * Turn off the entire system.
255 */
256static void
257apm_power_off(int howto, void *junk)
258{
259	struct apm_softc *sc = &apm_softc;
260
261	/* Not halting powering off, or not active */
262	if (!(howto & RB_POWEROFF) || !apm_softc.active)
263		return;
264	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
265	sc->bios.r.ebx = PMDV_ALLDEV;
266	sc->bios.r.ecx = PMST_OFF;
267	sc->bios.r.edx = 0;
268	(void) apm_bioscall();
269}
270
271/* APM Battery low handler */
272static void
273apm_battery_low(void)
274{
275	printf("\007\007 * * * BATTERY IS LOW * * * \007\007");
276}
277
278/* APM hook manager */
279static struct apmhook *
280apm_add_hook(struct apmhook **list, struct apmhook *ah)
281{
282	int s;
283	struct apmhook *p, *prev;
284
285#ifdef APM_DEBUG
286	printf("Add hook \"%s\"\n", ah->ah_name);
287#endif
288
289	s = splhigh();
290	if (ah == NULL)
291		panic("illegal apm_hook!");
292	prev = NULL;
293	for (p = *list; p != NULL; prev = p, p = p->ah_next)
294		if (p->ah_order > ah->ah_order)
295			break;
296
297	if (prev == NULL) {
298		ah->ah_next = *list;
299		*list = ah;
300	} else {
301		ah->ah_next = prev->ah_next;
302		prev->ah_next = ah;
303	}
304	splx(s);
305	return ah;
306}
307
308static void
309apm_del_hook(struct apmhook **list, struct apmhook *ah)
310{
311	int s;
312	struct apmhook *p, *prev;
313
314	s = splhigh();
315	prev = NULL;
316	for (p = *list; p != NULL; prev = p, p = p->ah_next)
317		if (p == ah)
318			goto deleteit;
319	panic("Tried to delete unregistered apm_hook.");
320	goto nosuchnode;
321deleteit:
322	if (prev != NULL)
323		prev->ah_next = p->ah_next;
324	else
325		*list = p->ah_next;
326nosuchnode:
327	splx(s);
328}
329
330
331/* APM driver calls some functions automatically */
332static void
333apm_execute_hook(struct apmhook *list)
334{
335	struct apmhook *p;
336
337	for (p = list; p != NULL; p = p->ah_next) {
338#ifdef APM_DEBUG
339		printf("Execute APM hook \"%s.\"\n", p->ah_name);
340#endif
341		if ((*(p->ah_fun))(p->ah_arg))
342			printf("Warning: APM hook \"%s\" failed", p->ah_name);
343	}
344}
345
346
347/* establish an apm hook */
348struct apmhook *
349apm_hook_establish(int apmh, struct apmhook *ah)
350{
351	if (apmh < 0 || apmh >= NAPM_HOOK)
352		return NULL;
353
354	return apm_add_hook(&hook[apmh], ah);
355}
356
357/* disestablish an apm hook */
358void
359apm_hook_disestablish(int apmh, struct apmhook *ah)
360{
361	if (apmh < 0 || apmh >= NAPM_HOOK)
362		return;
363
364	apm_del_hook(&hook[apmh], ah);
365}
366
367
368static struct timeval suspend_time;
369static struct timeval diff_time;
370
371static int
372apm_default_resume(void *arg)
373{
374	int pl;
375	u_int second, minute, hour;
376	struct timeval resume_time, tmp_time;
377
378	/* modified for adjkerntz */
379	pl = splsoftclock();
380	inittodr(0);			/* adjust time to RTC */
381	microtime(&resume_time);
382	getmicrotime(&tmp_time);
383	timevaladd(&tmp_time, &diff_time);
384
385#ifdef FIXME
386	/* XXX THIS DOESN'T WORK!!! */
387	time = tmp_time;
388#endif
389
390#ifdef APM_FIXUP_CALLTODO
391	/* Calculate the delta time suspended */
392	timevalsub(&resume_time, &suspend_time);
393	/* Fixup the calltodo list with the delta time. */
394	adjust_timeout_calltodo(&resume_time);
395#endif /* APM_FIXUP_CALLTODOK */
396	splx(pl);
397#ifndef APM_FIXUP_CALLTODO
398	second = resume_time.tv_sec - suspend_time.tv_sec;
399#else /* APM_FIXUP_CALLTODO */
400	/*
401	 * We've already calculated resume_time to be the delta between
402	 * the suspend and the resume.
403	 */
404	second = resume_time.tv_sec;
405#endif /* APM_FIXUP_CALLTODO */
406	hour = second / 3600;
407	second %= 3600;
408	minute = second / 60;
409	second %= 60;
410	log(LOG_NOTICE, "resumed from suspended mode (slept %02d:%02d:%02d)\n",
411		hour, minute, second);
412	return 0;
413}
414
415static int
416apm_default_suspend(void *arg)
417{
418	int	pl;
419
420	pl = splsoftclock();
421	microtime(&diff_time);
422	inittodr(0);
423	microtime(&suspend_time);
424	timevalsub(&diff_time, &suspend_time);
425	splx(pl);
426	return 0;
427}
428
429static int apm_record_event __P((struct apm_softc *, u_int));
430static void apm_processevent(void);
431
432static u_int apm_op_inprog = 0;
433
434static void
435apm_lastreq_notify(void)
436{
437	struct apm_softc *sc = &apm_softc;
438
439	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
440	sc->bios.r.ebx = PMDV_ALLDEV;
441	sc->bios.r.ecx = PMST_LASTREQNOTIFY;
442	sc->bios.r.edx = 0;
443	apm_bioscall();
444}
445
446static int
447apm_lastreq_rejected(void)
448{
449	struct apm_softc *sc = &apm_softc;
450
451	if (apm_op_inprog == 0) {
452		return 1;	/* no operation in progress */
453	}
454
455	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
456	sc->bios.r.ebx = PMDV_ALLDEV;
457	sc->bios.r.ecx = PMST_LASTREQREJECT;
458	sc->bios.r.edx = 0;
459
460	if (apm_bioscall()) {
461#ifdef APM_DEBUG
462		printf("apm_lastreq_rejected: failed\n");
463#endif
464		return 1;
465	}
466	apm_op_inprog = 0;
467	return 0;
468}
469
470/*
471 * Public interface to the suspend/resume:
472 *
473 * Execute suspend and resume hook before and after sleep, respectively.
474 *
475 */
476
477void
478apm_suspend(int state)
479{
480	struct apm_softc *sc = &apm_softc;
481	int error;
482
483	if (!sc)
484		return;
485
486	apm_op_inprog = 0;
487
488	if (sc->initialized) {
489		error = DEVICE_SUSPEND(root_bus);
490		/*
491		 * XXX Shouldn't ignore the error like this, but should
492		 * instead fix the newbus code.  Until that happens,
493		 * I'm doing this to get suspend working again.
494		 */
495		if (error)
496			printf("DEVICE_SUSPEND error %d, ignored\n", error);
497		apm_execute_hook(hook[APM_HOOK_SUSPEND]);
498		if (apm_suspend_system(state) == 0)
499			apm_processevent();
500		else
501			/* Failure, 'resume' the system again */
502			apm_execute_hook(hook[APM_HOOK_RESUME]);
503	}
504}
505
506void
507apm_resume(void)
508{
509	struct apm_softc *sc = &apm_softc;
510
511	if (!sc)
512		return;
513
514	if (sc->initialized) {
515		DEVICE_RESUME(root_bus);
516		apm_execute_hook(hook[APM_HOOK_RESUME]);
517	}
518}
519
520
521/* get APM information */
522static int
523apm_get_info(apm_info_t aip)
524{
525	struct apm_softc *sc = &apm_softc;
526
527	sc->bios.r.eax = (APM_BIOS << 8) | APM_GETPWSTATUS;
528	sc->bios.r.ebx = PMDV_ALLDEV;
529	sc->bios.r.ecx = 0;
530	sc->bios.r.edx = 0xffff;		/* default to unknown battery time */
531
532	if (apm_bioscall())
533		return 1;
534
535	aip->ai_infoversion = 1;
536	aip->ai_acline      = (sc->bios.r.ebx >> 8) & 0xff;
537	aip->ai_batt_stat   = sc->bios.r.ebx & 0xff;
538	aip->ai_batt_life   = sc->bios.r.ecx & 0xff;
539	aip->ai_major       = (u_int)sc->majorversion;
540	aip->ai_minor       = (u_int)sc->minorversion;
541	aip->ai_status      = (u_int)sc->active;
542	sc->bios.r.edx &= 0xffff;
543	if (sc->bios.r.edx == 0xffff)	/* Time is unknown */
544		aip->ai_batt_time = -1;
545	else if (sc->bios.r.edx & 0x8000)	/* Time is in minutes */
546		aip->ai_batt_time = (sc->bios.r.edx & 0x7fff) * 60;
547	else			/* Time is in seconds */
548		aip->ai_batt_time = sc->bios.r.edx;
549
550	sc->bios.r.eax = (APM_BIOS << 8) | APM_GETCAPABILITIES;
551	sc->bios.r.ebx = 0;
552	sc->bios.r.ecx = 0;
553	sc->bios.r.edx = 0;
554	if (apm_bioscall()) {
555		aip->ai_batteries = -1;	/* Unknown */
556		aip->ai_capabilities = 0xff00; /* Unknown, with no bits set */
557	} else {
558		aip->ai_batteries = sc->bios.r.ebx & 0xff;
559		aip->ai_capabilities = sc->bios.r.ecx & 0xf;
560	}
561
562	bzero(aip->ai_spare, sizeof aip->ai_spare);
563
564	return 0;
565}
566
567
568/* inform APM BIOS that CPU is idle */
569void
570apm_cpu_idle(void)
571{
572	struct apm_softc *sc = &apm_softc;
573
574	if (sc->active) {
575
576		sc->bios.r.eax = (APM_BIOS <<8) | APM_CPUIDLE;
577		sc->bios.r.edx = sc->bios.r.ecx = sc->bios.r.ebx = 0;
578		(void) apm_bioscall();
579	}
580	/*
581	 * Some APM implementation halts CPU in BIOS, whenever
582	 * "CPU-idle" function are invoked, but swtch() of
583	 * FreeBSD halts CPU, therefore, CPU is halted twice
584	 * in the sched loop. It makes the interrupt latency
585	 * terribly long and be able to cause a serious problem
586	 * in interrupt processing. We prevent it by removing
587	 * "hlt" operation from swtch() and managed it under
588	 * APM driver.
589	 */
590	if (!sc->active || sc->always_halt_cpu)
591		__asm("hlt");	/* wait for interrupt */
592}
593
594/* inform APM BIOS that CPU is busy */
595void
596apm_cpu_busy(void)
597{
598	struct apm_softc *sc = &apm_softc;
599
600	/*
601	 * The APM specification says this is only necessary if your BIOS
602	 * slows down the processor in the idle task, otherwise it's not
603	 * necessary.
604	 */
605	if (sc->slow_idle_cpu && sc->active) {
606
607		sc->bios.r.eax = (APM_BIOS <<8) | APM_CPUBUSY;
608		sc->bios.r.edx = sc->bios.r.ecx = sc->bios.r.ebx = 0;
609		apm_bioscall();
610	}
611}
612
613
614/*
615 * APM timeout routine:
616 *
617 * This routine is automatically called by timer once per second.
618 */
619
620static void
621apm_timeout(void *dummy)
622{
623	struct apm_softc *sc = &apm_softc;
624
625	if (apm_op_inprog)
626		apm_lastreq_notify();
627
628	if (!sc->bios_busy)
629		apm_processevent();
630
631	if (sc->active == 1)
632		/* Run slightly more oftan than 1 Hz */
633		apm_timeout_ch = timeout(apm_timeout, NULL, hz - 1 );
634}
635
636/* enable APM BIOS */
637static void
638apm_event_enable(void)
639{
640	struct apm_softc *sc = &apm_softc;
641
642#ifdef APM_DEBUG
643	printf("called apm_event_enable()\n");
644#endif
645	if (sc->initialized) {
646		sc->active = 1;
647		apm_timeout(sc);
648	}
649}
650
651/* disable APM BIOS */
652static void
653apm_event_disable(void)
654{
655	struct apm_softc *sc = &apm_softc;
656
657#ifdef APM_DEBUG
658	printf("called apm_event_disable()\n");
659#endif
660	if (sc->initialized) {
661		untimeout(apm_timeout, NULL, apm_timeout_ch);
662		sc->active = 0;
663	}
664}
665
666/* halt CPU in scheduling loop */
667static void
668apm_halt_cpu(void)
669{
670	struct apm_softc *sc = &apm_softc;
671
672	if (sc->initialized)
673		sc->always_halt_cpu = 1;
674}
675
676/* don't halt CPU in scheduling loop */
677static void
678apm_not_halt_cpu(void)
679{
680	struct apm_softc *sc = &apm_softc;
681
682	if (sc->initialized)
683		sc->always_halt_cpu = 0;
684}
685
686/* device driver definitions */
687
688/*
689 * probe for APM BIOS
690 */
691static int
692apm_probe(device_t dev)
693{
694#define APM_KERNBASE	KERNBASE
695	struct vm86frame	vmf;
696	struct apm_softc	*sc = &apm_softc;
697	int			disabled, flags;
698
699	if (resource_int_value("apm", 0, "disabled", &disabled) == 0
700	    && disabled != 0)
701		return ENXIO;
702
703	device_set_desc(dev, "APM BIOS");
704
705	if ( device_get_unit(dev) > 0 ) {
706		printf("apm: Only one APM driver supported.\n");
707		return ENXIO;
708	}
709
710	if (resource_int_value("apm", 0, "flags", &flags) != 0)
711		flags = 0;
712
713	bzero(&vmf, sizeof(struct vm86frame));		/* safety */
714	bzero(&apm_softc, sizeof(apm_softc));
715	vmf.vmf_ah = APM_BIOS;
716	vmf.vmf_al = APM_INSTCHECK;
717	vmf.vmf_bx = 0;
718	if (vm86_intcall(APM_INT, &vmf))
719		return ENXIO;			/* APM not found */
720	if (vmf.vmf_bx != 0x504d) {
721		printf("apm: incorrect signature (0x%x)\n", vmf.vmf_bx);
722		return ENXIO;
723	}
724	if ((vmf.vmf_cx & (APM_32BIT_SUPPORT | APM_16BIT_SUPPORT)) == 0) {
725		printf("apm: protected mode connections are not supported\n");
726		return ENXIO;
727	}
728
729	apm_version = vmf.vmf_ax;
730	sc->slow_idle_cpu = ((vmf.vmf_cx & APM_CPUIDLE_SLOW) != 0);
731	sc->disabled = ((vmf.vmf_cx & APM_DISABLED) != 0);
732	sc->disengaged = ((vmf.vmf_cx & APM_DISENGAGED) != 0);
733
734	vmf.vmf_ah = APM_BIOS;
735	vmf.vmf_al = APM_DISCONNECT;
736	vmf.vmf_bx = 0;
737        vm86_intcall(APM_INT, &vmf);		/* disconnect, just in case */
738
739	if ((vmf.vmf_cx & APM_32BIT_SUPPORT) != 0) {
740		vmf.vmf_ah = APM_BIOS;
741		vmf.vmf_al = APM_PROT32CONNECT;
742		vmf.vmf_bx = 0;
743		if (vm86_intcall(APM_INT, &vmf)) {
744			printf("apm: 32-bit connection error.\n");
745			return (ENXIO);
746 		}
747		sc->bios.seg.code32.base = (vmf.vmf_ax << 4) + APM_KERNBASE;
748		sc->bios.seg.code32.limit = 0xffff;
749		sc->bios.seg.code16.base = (vmf.vmf_cx << 4) + APM_KERNBASE;
750		sc->bios.seg.code16.limit = 0xffff;
751		sc->bios.seg.data.base = (vmf.vmf_dx << 4) + APM_KERNBASE;
752		sc->bios.seg.data.limit = 0xffff;
753		sc->bios.entry = vmf.vmf_ebx;
754		sc->connectmode = APM_PROT32CONNECT;
755 	} else {
756		/* use 16-bit connection */
757		vmf.vmf_ah = APM_BIOS;
758		vmf.vmf_al = APM_PROT16CONNECT;
759		vmf.vmf_bx = 0;
760		if (vm86_intcall(APM_INT, &vmf)) {
761			printf("apm: 16-bit connection error.\n");
762			return (ENXIO);
763		}
764		sc->bios.seg.code16.base = (vmf.vmf_ax << 4) + APM_KERNBASE;
765		sc->bios.seg.code16.limit = 0xffff;
766		sc->bios.seg.data.base = (vmf.vmf_cx << 4) + APM_KERNBASE;
767		sc->bios.seg.data.limit = 0xffff;
768		sc->bios.entry = vmf.vmf_bx;
769		sc->connectmode = APM_PROT16CONNECT;
770	}
771	return(0);
772}
773
774
775/*
776 * return 0 if the user will notice and handle the event,
777 * return 1 if the kernel driver should do so.
778 */
779static int
780apm_record_event(struct apm_softc *sc, u_int event_type)
781{
782	struct apm_event_info *evp;
783
784	if ((sc->sc_flags & SCFLAG_OPEN) == 0)
785		return 1;		/* no user waiting */
786	if (sc->event_count == APM_NEVENTS)
787		return 1;			/* overflow */
788	if (sc->event_filter[event_type] == 0)
789		return 1;		/* not registered */
790	evp = &sc->event_list[sc->event_ptr];
791	sc->event_count++;
792	sc->event_ptr++;
793	sc->event_ptr %= APM_NEVENTS;
794	evp->type = event_type;
795	evp->index = ++apm_evindex;
796	selwakeup(&sc->sc_rsel);
797	return (sc->sc_flags & SCFLAG_OCTL) ? 0 : 1; /* user may handle */
798}
799
800/* Process APM event */
801static void
802apm_processevent(void)
803{
804	int apm_event;
805	struct apm_softc *sc = &apm_softc;
806
807#ifdef APM_DEBUG
808#  define OPMEV_DEBUGMESSAGE(symbol) case symbol: \
809	printf("Received APM Event: " #symbol "\n");
810#else
811#  define OPMEV_DEBUGMESSAGE(symbol) case symbol:
812#endif
813	do {
814		apm_event = apm_getevent();
815		switch (apm_event) {
816		    OPMEV_DEBUGMESSAGE(PMEV_STANDBYREQ);
817			if (apm_op_inprog == 0) {
818			    apm_op_inprog++;
819			    if (apm_record_event(sc, apm_event)) {
820				apm_suspend(PMST_STANDBY);
821			    }
822			}
823			break;
824		    OPMEV_DEBUGMESSAGE(PMEV_SUSPENDREQ);
825 			apm_lastreq_notify();
826			if (apm_op_inprog == 0) {
827			    apm_op_inprog++;
828			    if (apm_record_event(sc, apm_event)) {
829				apm_suspend(PMST_SUSPEND);
830			    }
831			}
832			return; /* XXX skip the rest */
833		    OPMEV_DEBUGMESSAGE(PMEV_USERSUSPENDREQ);
834 			apm_lastreq_notify();
835			if (apm_op_inprog == 0) {
836			    apm_op_inprog++;
837			    if (apm_record_event(sc, apm_event)) {
838				apm_suspend(PMST_SUSPEND);
839			    }
840			}
841			return; /* XXX skip the rest */
842		    OPMEV_DEBUGMESSAGE(PMEV_CRITSUSPEND);
843			apm_suspend(PMST_SUSPEND);
844			break;
845		    OPMEV_DEBUGMESSAGE(PMEV_NORMRESUME);
846			apm_record_event(sc, apm_event);
847			apm_resume();
848			break;
849		    OPMEV_DEBUGMESSAGE(PMEV_CRITRESUME);
850			apm_record_event(sc, apm_event);
851			apm_resume();
852			break;
853		    OPMEV_DEBUGMESSAGE(PMEV_STANDBYRESUME);
854			apm_record_event(sc, apm_event);
855			apm_resume();
856			break;
857		    OPMEV_DEBUGMESSAGE(PMEV_BATTERYLOW);
858			if (apm_record_event(sc, apm_event)) {
859			    apm_battery_low();
860			    apm_suspend(PMST_SUSPEND);
861			}
862			break;
863		    OPMEV_DEBUGMESSAGE(PMEV_POWERSTATECHANGE);
864			apm_record_event(sc, apm_event);
865			break;
866		    OPMEV_DEBUGMESSAGE(PMEV_UPDATETIME);
867			apm_record_event(sc, apm_event);
868			inittodr(0);	/* adjust time to RTC */
869			break;
870		    case PMEV_NOEVENT:
871			break;
872		    default:
873			printf("Unknown Original APM Event 0x%x\n", apm_event);
874			    break;
875		}
876	} while (apm_event != PMEV_NOEVENT);
877}
878
879/*
880 * Attach APM:
881 *
882 * Initialize APM driver
883 */
884
885static int
886apm_attach(device_t dev)
887{
888	struct apm_softc	*sc = &apm_softc;
889	int			flags;
890	int			drv_version;
891
892	if (resource_int_value("apm", 0, "flags", &flags) != 0)
893		flags = 0;
894
895	if (flags & 0x20)
896		statclock_disable = 1;
897
898	sc->initialized = 0;
899
900	/* Must be externally enabled */
901	sc->active = 0;
902
903	/* Always call HLT in idle loop */
904	sc->always_halt_cpu = 1;
905
906	/* print bootstrap messages */
907#ifdef APM_DEBUG
908	printf("apm: APM BIOS version %04x\n",  apm_version);
909	printf("apm: Code16 0x%08x, Data 0x%08x\n",
910               sc->bios.seg.code16.base, sc->bios.seg.data.base);
911	printf("apm: Code entry 0x%08x, Idling CPU %s, Management %s\n",
912               sc->bios.entry, is_enabled(sc->slow_idle_cpu),
913	       is_enabled(!sc->disabled));
914	printf("apm: CS_limit=0x%x, DS_limit=0x%x\n",
915	      sc->bios.seg.code16.limit, sc->bios.seg.data.limit);
916#endif /* APM_DEBUG */
917
918#if 0
919	/*
920	 * XXX this may not be needed anymore
921	 */
922	if ((flags & 0x10)) {
923		if ((flags & 0xf) >= 0x2) {
924			apm_driver_version(0x102);
925		}
926		if (!apm_version && (flags & 0xf) >= 0x1) {
927			apm_driver_version(0x101);
928		}
929	} else {
930		apm_driver_version(0x102);
931		if (!apm_version)
932			apm_driver_version(0x101);
933	}
934#endif
935	/*
936        * In one test, apm bios version was 1.02; an attempt to register
937        * a 1.04 driver resulted in a 1.00 connection!  Registering a
938        * 1.02 driver resulted in a 1.02 connection.
939        */
940	drv_version = apm_version > 0x102 ? 0x102 : apm_version;
941	for (; drv_version > 0x100; drv_version--)
942		if (apm_driver_version(drv_version) == 0)
943			break;
944	sc->minorversion = ((drv_version & 0x00f0) >>  4) * 10 +
945		((drv_version & 0x000f) >> 0);
946	sc->majorversion = ((drv_version & 0xf000) >> 12) * 10 +
947		((apm_version & 0x0f00) >> 8);
948
949	sc->intversion = INTVERSION(sc->majorversion, sc->minorversion);
950
951#ifdef APM_DEBUG
952	if (sc->intversion >= INTVERSION(1, 1))
953		printf("apm: Engaged control %s\n", is_enabled(!sc->disengaged));
954#endif
955
956	printf("apm: found APM BIOS v%ld.%ld, connected at v%d.%d\n",
957	       ((apm_version & 0xf000) >> 12) * 10 + ((apm_version & 0x0f00) >> 8),
958	       ((apm_version & 0x00f0) >> 4) * 10 + ((apm_version & 0x000f) >> 0),
959	       sc->majorversion, sc->minorversion);
960
961#ifdef APM_DEBUG
962	printf("apm: Slow Idling CPU %s\n", is_enabled(sc->slow_idle_cpu));
963#endif
964
965	/* enable power management */
966	if (sc->disabled) {
967		if (apm_enable_disable_pm(1)) {
968#ifdef APM_DEBUG
969			printf("apm: *Warning* enable function failed! [%x]\n",
970				(sc->bios.r.eax >> 8) & 0xff);
971#endif
972		}
973	}
974
975	/* engage power managment (APM 1.1 or later) */
976	if (sc->intversion >= INTVERSION(1, 1) && sc->disengaged) {
977		if (apm_engage_disengage_pm(1)) {
978#ifdef APM_DEBUG
979			printf("apm: *Warning* engage function failed err=[%x]",
980				(sc->bios.r.eax >> 8) & 0xff);
981			printf(" (Docked or using external power?).\n");
982#endif
983		}
984	}
985
986        /* default suspend hook */
987        sc->sc_suspend.ah_fun = apm_default_suspend;
988        sc->sc_suspend.ah_arg = sc;
989        sc->sc_suspend.ah_name = "default suspend";
990        sc->sc_suspend.ah_order = APM_MAX_ORDER;
991
992        /* default resume hook */
993        sc->sc_resume.ah_fun = apm_default_resume;
994        sc->sc_resume.ah_arg = sc;
995        sc->sc_resume.ah_name = "default resume";
996        sc->sc_resume.ah_order = APM_MIN_ORDER;
997
998        apm_hook_establish(APM_HOOK_SUSPEND, &sc->sc_suspend);
999        apm_hook_establish(APM_HOOK_RESUME , &sc->sc_resume);
1000
1001	/* Power the system off using APM */
1002	at_shutdown_pri(apm_power_off, NULL, SHUTDOWN_FINAL, SHUTDOWN_PRI_LAST);
1003
1004	sc->initialized = 1;
1005
1006#ifdef DEVFS
1007	sc->sc_devfs_token =
1008		devfs_add_devswf(&apm_cdevsw, 0, DV_CHR, 0, 0, 0600, "apm");
1009#endif
1010	return 0;
1011}
1012
1013static int
1014apmopen(dev_t dev, int flag, int fmt, struct proc *p)
1015{
1016	struct apm_softc *sc = &apm_softc;
1017	int ctl = APMDEV(dev);
1018
1019	if (!sc->initialized)
1020		return (ENXIO);
1021
1022	switch (ctl) {
1023	case APMDEV_CTL:
1024		if (!(flag & FWRITE))
1025			return EINVAL;
1026		if (sc->sc_flags & SCFLAG_OCTL)
1027			return EBUSY;
1028		sc->sc_flags |= SCFLAG_OCTL;
1029		bzero(sc->event_filter, sizeof sc->event_filter);
1030		break;
1031	case APMDEV_NORMAL:
1032		sc->sc_flags |= SCFLAG_ONORMAL;
1033		break;
1034	default:
1035		return ENXIO;
1036		break;
1037	}
1038	return 0;
1039}
1040
1041static int
1042apmclose(dev_t dev, int flag, int fmt, struct proc *p)
1043{
1044	struct apm_softc *sc = &apm_softc;
1045	int ctl = APMDEV(dev);
1046
1047	switch (ctl) {
1048	case APMDEV_CTL:
1049		apm_lastreq_rejected();
1050		sc->sc_flags &= ~SCFLAG_OCTL;
1051		bzero(sc->event_filter, sizeof sc->event_filter);
1052		break;
1053	case APMDEV_NORMAL:
1054		sc->sc_flags &= ~SCFLAG_ONORMAL;
1055		break;
1056	}
1057	if ((sc->sc_flags & SCFLAG_OPEN) == 0) {
1058		sc->event_count = 0;
1059		sc->event_ptr = 0;
1060	}
1061	return 0;
1062}
1063
1064static int
1065apmioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct proc *p)
1066{
1067	struct apm_softc *sc = &apm_softc;
1068	struct apm_bios_arg *args;
1069	int error = 0;
1070	int newstate;
1071
1072	if (!sc->initialized)
1073		return (ENXIO);
1074#ifdef APM_DEBUG
1075	printf("APM ioctl: cmd = 0x%x\n", cmd);
1076#endif
1077	switch (cmd) {
1078	case APMIO_SUSPEND:
1079		if (sc->active)
1080			apm_suspend(PMST_SUSPEND);
1081		else
1082			error = EINVAL;
1083		break;
1084
1085	case APMIO_STANDBY:
1086		if (sc->active)
1087			apm_suspend(PMST_STANDBY);
1088		else
1089			error = EINVAL;
1090		break;
1091
1092	case APMIO_GETINFO_OLD:
1093		{
1094			struct apm_info info;
1095			apm_info_old_t aiop;
1096
1097			if (apm_get_info(&info))
1098				error = ENXIO;
1099			aiop = (apm_info_old_t)addr;
1100			aiop->ai_major = info.ai_major;
1101			aiop->ai_minor = info.ai_minor;
1102			aiop->ai_acline = info.ai_acline;
1103			aiop->ai_batt_stat = info.ai_batt_stat;
1104			aiop->ai_batt_life = info.ai_batt_life;
1105			aiop->ai_status = info.ai_status;
1106		}
1107		break;
1108	case APMIO_GETINFO:
1109		if (apm_get_info((apm_info_t)addr))
1110			error = ENXIO;
1111		break;
1112	case APMIO_ENABLE:
1113		apm_event_enable();
1114		break;
1115	case APMIO_DISABLE:
1116		apm_event_disable();
1117		break;
1118	case APMIO_HALTCPU:
1119		apm_halt_cpu();
1120		break;
1121	case APMIO_NOTHALTCPU:
1122		apm_not_halt_cpu();
1123		break;
1124	case APMIO_DISPLAY:
1125		newstate = *(int *)addr;
1126		if (apm_display(newstate))
1127			error = ENXIO;
1128		break;
1129	case APMIO_BIOS:
1130		/* XXX compatibility with the old interface */
1131		args = (struct apm_bios_arg *)addr;
1132		sc->bios.r.eax = args->eax;
1133		sc->bios.r.ebx = args->ebx;
1134		sc->bios.r.ecx = args->ecx;
1135		sc->bios.r.edx = args->edx;
1136		sc->bios.r.esi = args->esi;
1137		sc->bios.r.edi = args->edi;
1138		if (apm_bioscall())
1139			sc->bios.r.eax &= 0xff;
1140		args->eax = sc->bios.r.eax;
1141		args->ebx = sc->bios.r.ebx;
1142		args->ecx = sc->bios.r.ecx;
1143		args->edx = sc->bios.r.edx;
1144		args->esi = sc->bios.r.esi;
1145		args->edi = sc->bios.r.edi;
1146		break;
1147	default:
1148		error = EINVAL;
1149		break;
1150	}
1151
1152	/* for /dev/apmctl */
1153	if (APMDEV(dev) == APMDEV_CTL) {
1154		struct apm_event_info *evp;
1155		int i;
1156
1157		error = 0;
1158		switch (cmd) {
1159		case APMIO_NEXTEVENT:
1160			if (!sc->event_count) {
1161				error = EAGAIN;
1162			} else {
1163				evp = (struct apm_event_info *)addr;
1164				i = sc->event_ptr + APM_NEVENTS - sc->event_count;
1165				i %= APM_NEVENTS;
1166				*evp = sc->event_list[i];
1167				sc->event_count--;
1168			}
1169			break;
1170		case APMIO_REJECTLASTREQ:
1171			if (apm_lastreq_rejected()) {
1172				error = EINVAL;
1173			}
1174			break;
1175		default:
1176			error = EINVAL;
1177			break;
1178		}
1179	}
1180
1181	return error;
1182}
1183
1184static int
1185apmwrite(dev_t dev, struct uio *uio, int ioflag)
1186{
1187	struct apm_softc *sc = &apm_softc;
1188	u_int event_type;
1189	int error;
1190	u_char enabled;
1191
1192	if (APMDEV(dev) != APMDEV_CTL)
1193		return(ENODEV);
1194	if (uio->uio_resid != sizeof(u_int))
1195		return(E2BIG);
1196
1197	if ((error = uiomove((caddr_t)&event_type, sizeof(u_int), uio)))
1198		return(error);
1199
1200	if (event_type < 0 || event_type >= APM_NPMEV)
1201		return(EINVAL);
1202
1203	if (sc->event_filter[event_type] == 0) {
1204		enabled = 1;
1205	} else {
1206		enabled = 0;
1207	}
1208	sc->event_filter[event_type] = enabled;
1209#ifdef APM_DEBUG
1210	printf("apmwrite: event 0x%x %s\n", event_type, is_enabled(enabled));
1211#endif
1212
1213	return uio->uio_resid;
1214}
1215
1216static int
1217apmpoll(dev_t dev, int events, struct proc *p)
1218{
1219	struct apm_softc *sc = &apm_softc;
1220	int revents = 0;
1221
1222	if (events & (POLLIN | POLLRDNORM)) {
1223		if (sc->event_count) {
1224			revents |= events & (POLLIN | POLLRDNORM);
1225		} else {
1226			selrecord(p, &sc->sc_rsel);
1227		}
1228	}
1229
1230	return (revents);
1231}
1232
1233static device_method_t apm_methods[] = {
1234	/* Device interface */
1235	DEVMETHOD(device_probe,		apm_probe),
1236	DEVMETHOD(device_attach,	apm_attach),
1237
1238	{ 0, 0 }
1239};
1240
1241static driver_t apm_driver = {
1242	"apm",
1243	apm_methods,
1244	1,			/* no softc (XXX) */
1245};
1246
1247static devclass_t apm_devclass;
1248
1249DEV_DRIVER_MODULE(apm, nexus, apm_driver, apm_devclass, apm_cdevsw, 0, 0);
1250