apm.c revision 9540
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@mt.cs.keio.ac.jp>
6 *
7 * This software may be used, modified, copied, and distributed, in
8 * both source and binary form provided that the above copyright and
9 * these terms are retained. Under no circumstances is the author
10 * responsible for the proper functioning of this software, nor does
11 * the author assume any responsibility for damages incurred with its
12 * use.
13 *
14 * Sep, 1994	Implemented on FreeBSD 1.1.5.1R (Toshiba AVS001WD)
15 *
16 *	$Id: apm.c,v 1.12 1995/05/30 07:58:06 rgrimes Exp $
17 */
18
19#include "apm.h"
20
21#if NAPM > 0
22
23#ifdef __FreeBSD__
24#include <sys/param.h>
25#include "conf.h"
26#include <sys/kernel.h>
27#include <sys/systm.h>
28#include <sys/malloc.h>
29#include <sys/ioctl.h>
30#include <sys/file.h>
31#include <sys/proc.h>
32#include <sys/vnode.h>
33#include "i386/isa/isa.h"
34#include "i386/isa/isa_device.h"
35#include <machine/apm_bios.h>
36#include <machine/segments.h>
37#include <machine/clock.h>
38#include <vm/vm.h>
39#include <sys/syslog.h>
40#include "apm_setup.h"
41#endif /* __FreeBSD__ */
42
43#ifdef MACH_KERNEL
44#include <mach/std_types.h>
45#include <sys/types.h>
46#include <sys/time.h>
47#include <device/conf.h>
48#include <device/errno.h>
49#include <device/tty.h>
50#include <device/io_req.h>
51#include <kern/time_out.h>
52
53#include <i386/ipl.h>
54#include <i386/pio.h>
55#include <i386/seg.h>
56#include <i386/machspl.h>
57#include <chips/busses.h>
58#include <i386at/apm_bios.h>
59#include <i386at/apm_setup.h>
60#include <i386at/apm_segments.h>
61#endif /* MACH_KERNEL */
62
63/* static data */
64struct apm_softc {
65	int	initialized, active, halt_cpu;
66	u_int	minorversion, majorversion;
67	u_int	cs32_base, cs16_base, ds_base;
68	u_int	cs_limit, ds_limit;
69	u_int	cs_entry;
70	u_int	intversion;
71	int	idle_cpu, disabled, disengaged;
72	struct apmhook sc_suspend;
73	struct apmhook sc_resume;
74};
75
76static struct apm_softc apm_softc[NAPM];
77static struct apm_softc *master_softc = NULL; 	/* XXX */
78struct apmhook	*hook[NAPM_HOOK];		/* XXX */
79#ifdef APM_SLOWSTART
80int		apm_slowstart = 0;
81int		apm_ss_cnt = 0;
82static int	apm_slowstart_p = 0;
83int		apm_slowstart_stat = 0;
84#endif /* APM_SLOWSTART */
85
86#ifdef MACH_KERNEL
87extern struct fake_descriptor     gdt[GDTSZ];
88extern void fix_desc(struct fake_descriptor *, int);
89#endif /* MACH_KERNEL */
90
91#define is_enabled(foo) ((foo) ? "enabled" : "disabled")
92
93/* Map version number to integer (keeps ordering of version numbers) */
94#define INTVERSION(major, minor)	((major)*100 + (minor))
95
96#ifdef __FreeBSD__
97static timeout_t apm_timeout;
98#endif /* __FreeBSD__ */
99#ifdef MACH_KERNEL
100static void apm_timeout(void *);
101#endif /* MACH_KERNEL */
102
103/* setup APM GDT discriptors */
104static void
105setup_apm_gdt(u_int code32_base, u_int code16_base, u_int data_base, u_int code_limit, u_int data_limit)
106{
107#ifdef __FreeBSD__
108	/* setup 32bit code segment */
109	gdt_segs[GAPMCODE32_SEL].ssd_base  = code32_base;
110	gdt_segs[GAPMCODE32_SEL].ssd_limit = code_limit;
111
112	/* setup 16bit code segment */
113	gdt_segs[GAPMCODE16_SEL].ssd_base  = code16_base;
114	gdt_segs[GAPMCODE16_SEL].ssd_limit = code_limit;
115
116	/* setup data segment */
117	gdt_segs[GAPMDATA_SEL  ].ssd_base  = data_base;
118	gdt_segs[GAPMDATA_SEL  ].ssd_limit = data_limit;
119
120	/* reflect these changes on physical GDT */
121	ssdtosd(gdt_segs + GAPMCODE32_SEL, &gdt[GAPMCODE32_SEL].sd);
122	ssdtosd(gdt_segs + GAPMCODE16_SEL, &gdt[GAPMCODE16_SEL].sd);
123	ssdtosd(gdt_segs + GAPMDATA_SEL  , &gdt[GAPMDATA_SEL  ].sd);
124#endif /* __FreeBSD__ */
125#ifdef MACH_KERNEL
126	/* setup 32bit code segment */
127	gdt[sel_idx(GAPMCODE32_SEL)].offset       = code32_base;
128	gdt[sel_idx(GAPMCODE32_SEL)].lim_or_seg   = code_limit;
129	gdt[sel_idx(GAPMCODE32_SEL)].size_or_wdct = SZ_32;
130	gdt[sel_idx(GAPMCODE32_SEL)].access       = ACC_P | ACC_PL_K | ACC_CODE_R;
131
132	/* setup 16bit code segment */
133	gdt[sel_idx(GAPMCODE16_SEL)].offset       = code16_base;
134	gdt[sel_idx(GAPMCODE16_SEL)].lim_or_seg   = code_limit;
135	gdt[sel_idx(GAPMCODE16_SEL)].size_or_wdct = 0;
136	gdt[sel_idx(GAPMCODE16_SEL)].access       = ACC_P | ACC_PL_K | ACC_CODE_R;
137
138	/* setup data segment */
139	gdt[sel_idx(GAPMDATA_SEL  )].offset       = data_base;
140	gdt[sel_idx(GAPMDATA_SEL  )].lim_or_seg   = data_limit;
141	gdt[sel_idx(GAPMDATA_SEL  )].size_or_wdct = 0;
142	gdt[sel_idx(GAPMDATA_SEL  )].access       = ACC_P | ACC_PL_K | ACC_DATA_W;
143
144	/* reflect these changes on physical GDT */
145	fix_desc(gdt + sel_idx(GAPMCODE32_SEL), 1);
146	fix_desc(gdt + sel_idx(GAPMCODE16_SEL), 1);
147	fix_desc(gdt + sel_idx(GAPMDATA_SEL)  , 1);
148#endif /* MACH_KERNEL */
149}
150
151/* 48bit far pointer */
152struct addr48 {
153	u_long		offset;
154	u_short		segment;
155} apm_addr;
156
157int apm_errno;
158
159inline
160int
161apm_int(u_long *eax, u_long *ebx, u_long *ecx)
162{
163	u_long cf;
164	__asm ("pushl	%%ebp
165		pushl	%%edx
166		pushl	%%esi
167		xorl	%3,%3
168		movl	%3,%%esi
169		lcall	_apm_addr
170		jnc	1f
171		incl	%3
172	1:
173		popl	%%esi
174		popl	%%edx
175		popl	%%ebp"
176		: "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=D" (cf)
177		: "0" (*eax),  "1" (*ebx),  "2" (*ecx)
178		);
179	apm_errno = ((*eax) >> 8) & 0xff;
180	return cf;
181}
182
183
184/* enable/disable power management */
185static int
186apm_enable_disable_pm(struct apm_softc *sc, int enable)
187{
188	u_long eax, ebx, ecx;
189
190	eax = (APM_BIOS << 8) | APM_ENABLEDISABLEPM;
191
192	if (sc->intversion >= INTVERSION(1, 1)) {
193		ebx  = PMDV_ALLDEV;
194	} else {
195		ebx  = 0xffff;	/* APM version 1.0 only */
196	}
197	ecx  = enable;
198	return apm_int(&eax, &ebx, &ecx);
199}
200
201/* Tell APM-BIOS that WE will do 1.1 and see what they say... */
202static void
203apm_driver_version(void)
204{
205	u_long eax, ebx, ecx, i;
206
207#ifdef APM_DEBUG
208	eax = (APM_BIOS<<8) | APM_INSTCHECK;
209	ebx  = 0x0;
210	ecx  = 0x0101;
211	i = apm_int(&eax, &ebx, &ecx);
212	printf("[%04lx %04lx %04lx %ld %02x]\n",
213		eax, ebx, ecx, i, apm_errno);
214#endif
215
216	eax = (APM_BIOS << 8) | APM_DRVVERSION;
217	ebx  = 0x0;
218	ecx  = 0x0101;
219	if(!apm_int(&eax, &ebx, &ecx))
220		apm_version = eax & 0xffff;
221
222#ifdef APM_DEBUG
223	eax = (APM_BIOS << 8) | APM_INSTCHECK;
224	ebx  = 0x0;
225	ecx  = 0x0101;
226	i = apm_int(&eax, &ebx, &ecx);
227	printf("[%04lx %04lx %04lx %ld %02x]\n",
228		eax, ebx, ecx, i, apm_errno);
229#endif
230}
231
232/* engage/disengage power management (APM 1.1 or later) */
233static int
234apm_engage_disengage_pm(struct apm_softc *sc, int engage)
235{
236	u_long eax, ebx, ecx, i;
237
238	eax = (APM_BIOS << 8) | APM_ENGAGEDISENGAGEPM;
239	ebx = PMDV_ALLDEV;
240	ecx = engage;
241	i = apm_int(&eax, &ebx, &ecx);
242	return i;
243}
244
245/* get PM event */
246static u_int
247apm_getevent(struct apm_softc *sc)
248{
249	u_long eax, ebx, ecx;
250
251	eax = (APM_BIOS << 8) | APM_GETPMEVENT;
252
253	ebx = 0;
254	ecx = 0;
255	if (apm_int(&eax, &ebx, &ecx))
256		return PMEV_NOEVENT;
257
258	return ebx & 0xffff;
259}
260
261/* suspend entire system */
262static int
263apm_suspend_system(struct apm_softc *sc)
264{
265	u_long eax, ebx, ecx;
266
267	eax = (APM_BIOS << 8) | APM_SETPWSTATE;
268	ebx = PMDV_ALLDEV;
269	ecx = PMST_SUSPEND;
270
271	__asm("cli");
272	if (apm_int(&eax, &ebx, &ecx)) {
273		__asm("sti");
274		printf("Entire system suspend failure: errcode = %ld\n",
275			0xff & (eax >> 8));
276		return 1;
277	}
278	__asm("sti");
279	return 0;
280}
281
282/* Display control */
283/*
284 * Experimental implementation: My laptop machine can't handle this function
285 * If your laptop can control the display via APM, please inform me.
286 *                            HOSOKAWA, Tatsumi <hosokawa@mt.cs.keio.ac.jp>
287 */
288int
289apm_display_off(void)
290{
291	u_long eax, ebx, ecx;
292
293	eax = (APM_BIOS << 8) | APM_SETPWSTATE;
294	ebx = PMDV_2NDSTORAGE0;
295	ecx = PMST_STANDBY;
296	if (apm_int(&eax, &ebx, &ecx)) {
297		printf("Display off failure: errcode = %ld\n",
298			0xff & (eax >> 8));
299		return 1;
300	}
301
302	return 0;
303}
304
305/* APM Battery low handler */
306static void
307apm_battery_low(struct apm_softc *sc)
308{
309	printf("\007\007 * * * BATTERY IS LOW * * * \007\007");
310}
311
312/* APM hook manager */
313static struct apmhook *
314apm_add_hook(struct apmhook **list, struct apmhook *ah)
315{
316	int s;
317	struct apmhook *p, *prev;
318
319#if 0
320	printf("Add hook \"%s\"\n", ah->ah_name);
321#endif
322
323	s = splhigh();
324	if (ah == NULL) {
325		panic("illegal apm_hook!");
326	}
327	prev = NULL;
328	for (p = *list; p != NULL; prev = p, p = p->ah_next) {
329		if (p->ah_order > ah->ah_order) {
330			break;
331		}
332	}
333
334	if (prev == NULL) {
335		ah->ah_next = *list;
336		*list = ah;
337	} else {
338		ah->ah_next = prev->ah_next;
339		prev->ah_next = ah;
340	}
341	splx(s);
342	return ah;
343}
344
345static void
346apm_del_hook(struct apmhook **list, struct apmhook *ah)
347{
348	int s;
349	struct apmhook *p, *prev;
350
351	s = splhigh();
352	prev = NULL;
353	for (p = *list; p != NULL; prev = p, p = p->ah_next) {
354		if (p == ah) {
355			goto deleteit;
356		}
357	}
358	panic("Tried to delete unregistered apm_hook.");
359	goto nosuchnode;
360deleteit:
361	if (prev != NULL) {
362		prev->ah_next = p->ah_next;
363	} else {
364		*list = p->ah_next;
365	}
366nosuchnode:
367	splx(s);
368}
369
370
371/* APM driver calls some functions automatically */
372static void
373apm_execute_hook(struct apmhook *list)
374{
375	struct apmhook *p;
376
377	for (p = list; p != NULL; p = p->ah_next) {
378#if 0
379		printf("Execute APM hook \"%s.\"\n", p->ah_name);
380#endif
381		if ((*(p->ah_fun))(p->ah_arg)) {
382			printf("Warning: APM hook \"%s\" failed", p->ah_name);
383		}
384	}
385}
386
387
388/* establish an apm hook */
389struct apmhook *
390apm_hook_establish(int apmh, struct apmhook *ah)
391{
392	if (apmh < 0 || apmh >= NAPM_HOOK)
393		return NULL;
394
395	return apm_add_hook(&hook[apmh], ah);
396}
397
398/* disestablish an apm hook */
399void
400apm_hook_disestablish(int apmh, struct apmhook *ah)
401{
402	if (apmh < 0 || apmh >= NAPM_HOOK)
403		return;
404
405	apm_del_hook(&hook[apmh], ah);
406}
407
408
409static struct timeval suspend_time;
410static struct timeval diff_time;
411
412static int
413apm_default_resume(struct apm_softc *sc)
414{
415#ifdef __FreeBSD__
416	int pl;
417	u_int second, minute, hour;
418	struct timeval resume_time, tmp_time;
419
420	/* modified for adjkerntz */
421	pl = splsoftclock();
422	inittodr(0);			/* adjust time to RTC */
423	microtime(&resume_time);
424	tmp_time = time;		/* because 'time' is volatile */
425	timevaladd(&tmp_time, &diff_time);
426	time = tmp_time;
427	splx(pl);
428	second = resume_time.tv_sec - suspend_time.tv_sec;
429	hour = second / 3600;
430	second %= 3600;
431	minute = second / 60;
432	second %= 60;
433	log(LOG_NOTICE, "resumed from suspended mode (slept %02d:%02d:%02d)\n",
434		hour, minute, second);
435#endif /* __FreeBSD__ */
436	return 0;
437}
438
439static int
440apm_default_suspend(void)
441{
442#ifdef __FreeBSD__
443	int	pl;
444
445	pl = splsoftclock();
446	microtime(&diff_time);
447	inittodr(0);
448	microtime(&suspend_time);
449	timevalsub(&diff_time, &suspend_time);
450	splx(pl);
451#if 0
452	printf("diff_time = %d:%d\n", diff_time.tv_sec, diff_time.tv_usec);
453#endif
454#endif /* __FreeBSD__ */
455	return 0;
456}
457
458static void apm_processevent(struct apm_softc *);
459
460/*
461 * Public interface to the suspend/resume:
462 *
463 * Execute suspend and resume hook before and after sleep, respectively.
464 *
465 */
466
467void
468apm_suspend(void)
469{
470	struct apm_softc *sc;
471
472	sc = master_softc;      /* XXX */
473	if (!sc)
474		return;
475
476	if (sc->initialized) {
477		apm_execute_hook(hook[APM_HOOK_SUSPEND]);
478		apm_suspend_system(sc);
479		apm_processevent(sc);
480	}
481}
482
483void
484apm_resume(void)
485{
486	struct apm_softc *sc;
487
488	sc = master_softc;      /* XXX */
489	if (!sc)
490		return;
491
492	if (sc->initialized) {
493		apm_execute_hook(hook[APM_HOOK_RESUME]);
494	}
495}
496
497
498/* get APM information */
499static int
500apm_get_info(struct apm_softc *sc, apm_info_t aip)
501{
502	u_long eax, ebx, ecx;
503
504	eax = (APM_BIOS << 8) | APM_GETPWSTATUS;
505	ebx = PMDV_ALLDEV;
506	ecx = 0;
507
508	if (apm_int(&eax, &ebx, &ecx))
509		return 1;
510
511	aip->ai_acline    = (ebx >> 8) & 0xff;
512	aip->ai_batt_stat = ebx & 0xff;
513	aip->ai_batt_life = ecx & 0xff;
514	aip->ai_major     = (u_int)sc->majorversion;
515	aip->ai_minor     = (u_int)sc->minorversion;
516	return 0;
517}
518
519
520/* inform APM BIOS that CPU is idle */
521void
522apm_cpu_idle(void)
523{
524	struct apm_softc *sc = master_softc;    /* XXX */
525
526	if (sc->idle_cpu) {
527		if (sc->active) {
528			__asm ("movw $0x5305, %ax; lcall _apm_addr");
529		}
530	}
531	/*
532	 * Some APM implementation halts CPU in BIOS, whenever
533	 * "CPU-idle" function are invoked, but swtch() of
534	 * FreeBSD halts CPU, therefore, CPU is halted twice
535	 * in the sched loop. It makes the interrupt latency
536	 * terribly long and be able to cause a serious problem
537	 * in interrupt processing. We prevent it by removing
538	 * "hlt" operation from swtch() and managed it under
539	 * APM driver.
540	 */
541	/*
542	 * UKAI Note: on NetBSD, idle() called from cpu_switch()
543	 * doesn't halt CPU, so halt_cpu may not need on NetBSD/i386
544	 * or only "sti" operation would be needed.
545	 */
546
547	if (!sc->active || sc->halt_cpu) {
548		__asm("sti ; hlt");	/* wait for interrupt */
549	}
550}
551
552/* inform APM BIOS that CPU is busy */
553void
554apm_cpu_busy(void)
555{
556	struct apm_softc *sc = master_softc;	/* XXX */
557
558	if (sc->idle_cpu && sc->active) {
559		__asm("movw $0x5306, %ax; lcall _apm_addr");
560	}
561}
562
563
564/*
565 * APM timeout routine:
566 *
567 * This routine is automatically called by timer once per second.
568 */
569
570static void
571apm_timeout(void *arg)
572{
573	struct apm_softc *sc = arg;
574
575	apm_processevent(sc);
576	timeout(apm_timeout, (void *)sc, hz - 1 );  /* More than 1 Hz */
577}
578
579/* enable APM BIOS */
580static void
581apm_event_enable(struct apm_softc *sc)
582{
583#ifdef APM_DEBUG
584	printf("called apm_event_enable()\n");
585#endif
586	if (sc->initialized) {
587		sc->active = 1;
588		apm_timeout(sc);
589	}
590}
591
592/* disable APM BIOS */
593static void
594apm_event_disable(struct apm_softc *sc)
595{
596#ifdef APM_DEBUG
597	printf("called apm_event_disable()\n");
598#endif
599	if (sc->initialized) {
600		untimeout(apm_timeout, NULL);
601		sc->active = 0;
602	}
603}
604
605/* halt CPU in scheduling loop */
606static void
607apm_halt_cpu(struct apm_softc *sc)
608{
609	if (sc->initialized) {
610		sc->halt_cpu = 1;
611	}
612#ifdef APM_SLOWSTART
613	apm_slowstart = 0;
614#endif /* APM_SLOWSTART */
615}
616
617/* don't halt CPU in scheduling loop */
618static void
619apm_not_halt_cpu(struct apm_softc *sc)
620{
621	if (sc->initialized) {
622		sc->halt_cpu = 0;
623	}
624#ifdef APM_SLOWSTART
625	apm_slowstart = apm_slowstart_p;
626#endif /* APM_SLOWSTART */
627}
628
629/* device driver definitions */
630#ifdef __FreeBSD__
631int apmprobe (struct isa_device *);
632int apmattach(struct isa_device *);
633struct isa_driver apmdriver = {
634	apmprobe, apmattach, "apm" };
635#endif /* __FreeBSD__ */
636
637#ifdef MACH_KERNEL
638int apmprobe(vm_offset_t, struct bus_ctlr *);
639void apmattach(struct bus_device *);
640static struct bus_device *apminfo[NAPM];
641static vm_offset_t apmstd[NAPM] = { 0 };
642struct bus_driver apmdriver = {
643	apmprobe, 0, apmattach, 0, apmstd, "apm", apminfo, 0, 0, 0};
644#endif /* MACH_KERNEL */
645
646
647/*
648 * probe APM (dummy):
649 *
650 * APM probing routine is placed on locore.s and apm_init.S because
651 * this process forces the CPU to turn to real mode or V86 mode.
652 * Current version uses real mode, but on future version, we want
653 * to use V86 mode in APM initialization.
654 */
655
656int
657#ifdef __FreeBSD__
658	apmprobe(struct isa_device *dvp)
659#endif /* __FreeBSD__ */
660#ifdef MACH_KERNEL
661	apmprobe(vm_offset_t port, struct bus_ctlr *devc)
662#endif /* MACH_KERNEL */
663{
664#ifdef __FreeBSD__
665	int     unit = dvp->id_unit;
666#endif /* __FreeBSD__ */
667#ifdef MACH_KERNEL
668	int     unit = devc->unit;
669#endif /* MACH_KERNEL */
670	struct apm_softc *sc = &apm_softc[unit];
671
672	switch (apm_version) {
673	case APMINI_CANTFIND:
674		/* silent */
675		return 0;
676	case APMINI_NOT32BIT:
677		printf("apm%d: 32bit connection is not supported.\n", unit);
678		return 0;
679	case APMINI_CONNECTERR:
680		printf("apm%d: 32-bit connection error.\n", unit);
681		return 0;
682	}
683
684	return -1;
685}
686
687
688/* Process APM event */
689static void
690apm_processevent(struct apm_softc *sc)
691{
692	int apm_event;
693
694#ifdef APM_DEBUG
695#  define OPMEV_DEBUGMESSAGE(symbol) case symbol: \
696	printf("Original APM Event: " #symbol "\n");
697#else
698#  define OPMEV_DEBUGMESSAGE(symbol) case symbol:
699#endif
700	while (1) {
701		apm_event = apm_getevent(sc);
702		if (apm_event == PMEV_NOEVENT)
703			break;
704		switch (apm_event) {
705		    OPMEV_DEBUGMESSAGE(PMEV_STANDBYREQ);
706			apm_suspend();
707			break;
708		    OPMEV_DEBUGMESSAGE(PMEV_SUSPENDREQ);
709			apm_suspend();
710			break;
711		    OPMEV_DEBUGMESSAGE(PMEV_USERSUSPENDREQ);
712			apm_suspend();
713			break;
714		    OPMEV_DEBUGMESSAGE(PMEV_CRITSUSPEND);
715			apm_suspend();
716			break;
717		    OPMEV_DEBUGMESSAGE(PMEV_NORMRESUME);
718			apm_resume();
719			break;
720		    OPMEV_DEBUGMESSAGE(PMEV_CRITRESUME);
721			apm_resume();
722			break;
723		    OPMEV_DEBUGMESSAGE(PMEV_STANDBYRESUME);
724			apm_resume();
725			break;
726		    OPMEV_DEBUGMESSAGE(PMEV_BATTERYLOW);
727			apm_battery_low(sc);
728			apm_suspend();
729			break;
730
731		    OPMEV_DEBUGMESSAGE(PMEV_POWERSTATECHANGE);
732			break;
733
734		    OPMEV_DEBUGMESSAGE(PMEV_UPDATETIME);
735			inittodr(0);	/* adjust time to RTC */
736			break;
737
738		    default:
739			printf("Unknown Original APM Event 0x%x\n", apm_event);
740			    break;
741		}
742	}
743}
744
745/*
746 * Attach APM:
747 *
748 * Initialize APM driver (APM BIOS itself has been initialized in locore.s)
749 *
750 * Now, unless I'm mad, (not quite ruled out yet), the APM-1.1 spec is bogus:
751 *
752 * Appendix C says under the header "APM 1.0/APM 1.1 Modal BIOS Behavior"
753 * that "When an APM Driver connects with an APM 1.1 BIOS, the APM 1.1 BIOS
754 * will default to an APM 1.0 connection.  After an APM Driver calls the APM
755 * Driver Version function, specifying that it supports APM 1.1, and [sic!]
756 * APM BIOS will change its behavior to an APM 1.1 connection.  If the APM
757 * BIOS is an APM 1.0 BIOS, the APM Driver Version function call will fail,
758 * and the connection will remain an APM 1.0 connection."
759 *
760 * OK so I can establish a 1.0 connection, and then tell that I'm a 1.1
761 * and maybe then the BIOS will tell that it too is a 1.1.
762 * Fine.
763 * Now how will I ever get the segment-limits for instance ?  There is no
764 * way I can see that I can get a 1.1 response back from an "APM Protected
765 * Mode 32-bit Interface Connect" function ???
766 *
767 * Who made this,  Intel and Microsoft ?  -- How did you guess !
768 *
769 * /phk
770 */
771
772#ifdef __FreeBSD__
773int
774apmattach(struct isa_device *dvp)
775#endif /* __FreeBSD__ */
776#ifdef MACH_KERNEL
777void
778apmattach(struct bus_device *dvp)
779#endif /* MACH_KERNEL */
780{
781#ifdef __FreeBSD__
782	int	unit = dvp->id_unit;
783#define APM_KERNBASE	KERNBASE
784#endif /* __FreeBSD__ */
785#ifdef MACH_KERNEL
786	int	unit = dvp->unit;
787#define APM_KERNBASE	VM_MIN_KERNEL_ADDRESS
788#endif /* MACH_KERNEL */
789	struct apm_softc	*sc = &apm_softc[unit];
790	int	i;
791
792	master_softc = sc;	/* XXX */
793	sc->initialized = 0;
794	sc->active = 0;
795	sc->halt_cpu = 1;
796
797	/* setup APM parameters */
798	sc->cs16_base = (apm_cs32_base << 4) + APM_KERNBASE;
799	sc->cs32_base = (apm_cs16_base << 4) + APM_KERNBASE;
800	sc->ds_base = (apm_ds_base << 4) + APM_KERNBASE;
801	sc->cs_limit = apm_cs_limit;
802	sc->ds_limit = apm_ds_limit;
803	sc->cs_entry = apm_cs_entry;
804
805	sc->idle_cpu = ((apm_flags & APM_CPUIDLE_SLOW) != 0);
806	sc->disabled = ((apm_flags & APM_DISABLED) != 0);
807	sc->disengaged = ((apm_flags & APM_DISENGAGED) != 0);
808
809#ifdef APM_SLOWSTART
810	if (sc->idle_cpu) {
811		apm_slowstart = apm_slowstart_p = 1;
812	}
813#endif
814
815	/* print bootstrap messages */
816#ifdef APM_DEBUG
817	printf(" found APM BIOS version %04x\n",  apm_version);
818	printf("apm%d: Code32 0x%08x, Code16 0x%08x, Data 0x%08x\n",
819		unit, sc->cs32_base, sc->cs16_base, sc->ds_base);
820	printf("apm%d: Code entry 0x%08x, Idling CPU %s, Management %s\n",
821		unit, sc->cs_entry, is_enabled(sc->idle_cpu),
822		is_enabled(!sc->disabled));
823	printf("apm%d: CS_limit=%x, DS_limit=%x\n",
824		unit, sc->cs_limit, sc->ds_limit);
825#endif /* APM_DEBUG */
826
827	sc->cs_limit = 0xffff;
828	sc->ds_limit = 0xffff;
829
830	/* setup GDT */
831	setup_apm_gdt(sc->cs32_base, sc->cs16_base, sc->ds_base,
832			sc->cs_limit, sc->ds_limit);
833
834	/* setup entry point 48bit pointer */
835#ifdef __FreeBSD__
836	apm_addr.segment = GSEL(GAPMCODE32_SEL, SEL_KPL);
837#endif /* __FreeBSD__ */
838#ifdef MACH_KERNEL
839	apm_addr.segment = GAPMCODE32_SEL;
840#endif /* MACH_KERNEL */
841	apm_addr.offset  = sc->cs_entry;
842
843	/* Try to kick bios into 1.1 mode */
844	apm_driver_version();
845	sc->minorversion = ((apm_version & 0x00f0) >>  4) * 10 +
846			((apm_version & 0x000f) >> 0);
847	sc->majorversion = ((apm_version & 0xf000) >> 12) * 10 +
848			((apm_version & 0x0f00) >> 8);
849
850	sc->intversion = INTVERSION(sc->majorversion, sc->minorversion);
851
852	if (sc->intversion >= INTVERSION(1, 1)) {
853		printf("apm%d: Engaged control %s\n",
854			unit, is_enabled(!sc->disengaged));
855	}
856
857	printf(" found APM BIOS version %d.%d\n",
858		sc->majorversion, sc->minorversion);
859	printf("apm%d: Idling CPU %s\n", unit, is_enabled(sc->idle_cpu));
860
861	/* enable power management */
862	if (sc->disabled) {
863		if (apm_enable_disable_pm(sc, 1)) {
864			printf("Warning: APM enable function failed! [%x]\n",
865				apm_errno);
866		}
867	}
868
869	/* engage power managment (APM 1.1 or later) */
870	if (sc->intversion >= INTVERSION(1, 1) && sc->disengaged) {
871		if (apm_engage_disengage_pm(sc, 1)) {
872			printf("Warning: APM engage function failed [%x]\n",
873				apm_errno);
874		}
875	}
876
877        /* default suspend hook */
878        sc->sc_suspend.ah_fun = apm_default_suspend;
879        sc->sc_suspend.ah_arg = sc;
880        sc->sc_suspend.ah_name = "default suspend";
881        sc->sc_suspend.ah_order = APM_MAX_ORDER;
882
883        /* default resume hook */
884        sc->sc_resume.ah_fun = apm_default_resume;
885        sc->sc_resume.ah_arg = sc;
886        sc->sc_resume.ah_name = "default resume";
887        sc->sc_resume.ah_order = APM_MIN_ORDER;
888
889        apm_hook_establish(APM_HOOK_SUSPEND, &sc->sc_suspend);
890        apm_hook_establish(APM_HOOK_RESUME , &sc->sc_resume);
891
892	apm_event_enable(sc);
893
894	sc->initialized = 1;
895
896#ifdef __FreeBSD__
897	return 0;
898#endif /* __FreeBSD__ */
899}
900
901#ifdef __FreeBSD__
902int
903apmopen(dev_t dev, int flag, int fmt, struct proc *p)
904{
905	struct apm_softc *sc = &apm_softc[minor(dev)];
906
907	if (minor(dev) >= NAPM) {
908		return (ENXIO);
909	}
910	if (!sc->initialized) {
911		return ENXIO;
912	}
913	return 0;
914}
915
916int
917apmclose(dev_t dev, int flag, int fmt, struct proc *p)
918{
919	return 0;
920}
921
922int
923apmioctl(dev_t dev, int cmd, caddr_t addr, int flag, struct proc *p)
924{
925	struct apm_softc *sc = &apm_softc[minor(dev)];
926	int error = 0;
927	int pl;
928
929#ifdef APM_DEBUG
930	printf("APM ioctl: minor = %d, cmd = 0x%x\n", minor(dev), cmd);
931#endif
932
933	if (minor(dev) >= NAPM) {
934		return ENXIO;
935	}
936	if (!sc->initialized) {
937		return ENXIO;
938	}
939	switch (cmd) {
940	case APMIO_SUSPEND:
941		apm_suspend();
942		break;
943	case APMIO_GETINFO:
944		if (apm_get_info(sc, (apm_info_t)addr)) {
945			error = ENXIO;
946		}
947		break;
948	case APMIO_ENABLE:
949		apm_event_enable(sc);
950		break;
951	case APMIO_DISABLE:
952		apm_event_disable(sc);
953		break;
954	case APMIO_HALTCPU:
955		apm_halt_cpu(sc);
956		break;
957	case APMIO_NOTHALTCPU:
958		apm_not_halt_cpu(sc);
959		break;
960	case APMIO_DISPLAYOFF:
961		if (apm_display_off()) {
962			error = ENXIO;
963		}
964		break;
965	default:
966		error = EINVAL;
967		break;
968	}
969	return error;
970}
971#endif /* __FreeBSD__ */
972
973#ifdef MACH_KERNEL
974io_return_t apmopen(dev_t dev, int flag, io_req_t ior)
975{
976	int result;
977
978	result = ENXIO;
979	return result;
980}
981
982io_return_t apmclose(dev_t dev, int flag)
983{
984	return 0;
985}
986
987io_return_t apmgetstat(dev_t dev, int flavor, int *data, u_int *count)
988{
989}
990
991io_return_t apmsetstat(dev_t dev, int flavor, int *data, u_int count)
992{
993}
994#endif /* MACH_KERNEL */
995
996#endif /* NAPM > 0 */
997