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