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