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