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