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