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