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