apm.c revision 3287
1/*
2 * LP (Laptop Package)
3 *
4 * Copyright (c) 1994 by HOSOKAWA, Tatsumi <hosokawa@mt.cs.keio.ac.jp>
5 *
6 * This software may be used, modified, copied, and distributed, in
7 * both source and binary form provided that the above copyright and
8 * these terms are retained. Under no circumstances is the author
9 * responsible for the proper functioning of this software, nor does
10 * the author assume any responsibility for damages incurred with its
11 * use.
12 *
13 * Sep, 1994	Implemented on FreeBSD 1.1.5.1R (Toshiba AVS001WD)
14 *
15 *	$Id: apm.c,v 1.3 1994/10/01 05:12:22 davidg Exp $
16 */
17
18#include "apm.h"
19
20#if NAPM > 0
21
22#include <sys/param.h>
23#include "conf.h"
24#include <sys/kernel.h>
25#include <sys/systm.h>
26#include <sys/malloc.h>
27#include <sys/ioctl.h>
28#include <sys/tty.h>
29#include <sys/file.h>
30#include <sys/proc.h>
31#include <sys/vnode.h>
32#include "i386/isa/isa.h"
33#include "i386/isa/isa_device.h"
34#include <machine/apm_bios.h>
35#include <machine/segments.h>
36#include <vm/vm.h>
37#include <sys/syslog.h>
38#include "apm_setup.h"
39
40/* static data */
41static int	apm_initialized = 0, active = 0, halt_cpu = 1;
42static u_int	minorversion, majorversion;
43static u_int	cs32_base, cs16_base, ds_base;
44static u_int	cs_limit, ds_limit;
45static u_int	cs_entry;
46static u_int	intversion;
47static int	idle_cpu, disabled, disengaged;
48
49/* Map version number to integer (keeps ordering of version numbers) */
50#define INTVERSION(major, minor)	((major)*100 + (minor))
51
52static timeout_t apm_timeout;
53
54/* setup APM GDT discriptors */
55static void
56setup_apm_gdt(u_int code32_base, u_int code16_base, u_int data_base, u_int code_limit, u_int data_limit)
57{
58	/* setup 32bit code segment */
59	gdt_segs[GAPMCODE32_SEL].ssd_base  = code32_base;
60	gdt_segs[GAPMCODE32_SEL].ssd_limit = code_limit;
61
62	/* setup 16bit code segment */
63	gdt_segs[GAPMCODE16_SEL].ssd_base  = code16_base;
64	gdt_segs[GAPMCODE16_SEL].ssd_limit = code_limit;
65
66	/* setup data segment */
67	gdt_segs[GAPMDATA_SEL  ].ssd_base  = data_base;
68	gdt_segs[GAPMDATA_SEL  ].ssd_limit = data_limit;
69
70	/* reflect these changes on physical GDT */
71	ssdtosd(gdt_segs + GAPMCODE32_SEL, gdt + GAPMCODE32_SEL);
72	ssdtosd(gdt_segs + GAPMCODE16_SEL, gdt + GAPMCODE16_SEL);
73	ssdtosd(gdt_segs + GAPMDATA_SEL  , gdt + GAPMDATA_SEL  );
74}
75
76/* 48bit far pointer */
77struct addr48 {
78	u_long		offset;
79	u_short		segment;
80} apm_addr;
81
82int apm_errno;
83
84inline
85int
86apm_int(u_long *eax,u_long *ebx,u_long *ecx)
87{
88	u_long cf;
89	__asm ("pushl	%%ebp
90		pushl	%%edx
91		xorl	%3,%3
92		lcall	_apm_addr
93		jnc	1f
94		incl	%3
95	1:	popl	%%edx
96		popl	%%ebp"
97		: "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=D" (cf)
98		: "0" (*eax),  "1" (*ebx),  "2" (*ecx)
99		);
100	apm_errno = ((*eax) >> 8) & 0xff;
101	return cf;
102}
103
104
105/* enable/disable power management */
106static int
107apm_enable_disable_pm(int enable)
108{
109	u_long eax,ebx,ecx;
110
111	eax = (APM_BIOS<<8) | APM_ENABLEDISABLEPM;
112
113	if (intversion >= INTVERSION(1, 1)) {
114		ebx  = PMDV_ALLDEV;
115	} else {
116		ebx  = 0xffff;	/* APM version 1.0 only */
117	}
118	ecx  = enable;
119	return apm_int(&eax,&ebx,&ecx);
120}
121
122/* engage/disengage power management (APM 1.1 or later) */
123static int
124apm_engage_disengage_pm(int engage)
125{
126	u_long eax,ebx,ecx,i;
127
128	eax = (APM_BIOS<<8) | APM_ENGAGEDISENGAGEPM;
129	ebx = PMDV_ALLDEV;
130	ecx = engage;
131	i = apm_int(&eax,&ebx,&ecx);
132	return i;
133}
134
135/* get PM event */
136static u_int
137apm_getevent(void)
138{
139	u_long eax,ebx,ecx;
140
141	eax = (APM_BIOS<<8) | APM_GETPMEVENT;
142
143	if (apm_int(&eax,&ebx,&ecx))
144		return PMEV_NOEVENT;
145	return ebx & 0xffff;
146}
147
148/*
149 * In many cases, the first event that occured after resume, needs
150 * special treatment. This binary flag make this process possible.
151 * Initial value of this variable is 1, because the bootstrap
152 * condition is equivalent to resumed condition for the power
153 * manager.
154 */
155static int	resumed_event = 1;
156
157/* suspend entire system */
158static int
159apm_suspend_system(void)
160{
161	u_long eax,ebx,ecx;
162
163	eax = (APM_BIOS<<8) | APM_SETPWSTATE;
164	ebx = PMDV_ALLDEV;
165	ecx = PMST_SUSPEND;
166
167	if (apm_int(&eax,&ebx,&ecx)) {
168		printf("Entire system suspend failure: errcode = %ld\n",
169			0xff & (eax >> 8));
170		return 1;
171	}
172	resumed_event = 1;
173	return 0;
174}
175
176/* APM Battery low handler */
177static void
178apm_battery_low(void)
179{
180	/* Currently, this routine has not been implemented. Sorry... */
181}
182
183
184/* APM driver calls some functions automatically when the system wakes up */
185static void
186apm_execute_hook(apm_hook_func_t list)
187{
188	apm_hook_func_t p;
189
190	for (p = list; p != NULL; p = p->next) {
191		if ((*(p->func))()) {
192			printf("Warning: APM hook of %s failed", p->name);
193		}
194	}
195}
196
197
198/* APM hook manager */
199static apm_hook_func_t
200apm_hook_init(apm_hook_func_t *list, int (*func)(void), char *name, int order)
201{
202	int pl;
203	apm_hook_func_t p, prev, new_node;
204
205	pl = splhigh();
206	new_node = malloc(sizeof(*new_node), M_DEVBUF, M_NOWAIT);
207	if (new_node == NULL) {
208		panic("Can't allocate device buffer for apm_resume_hook.");
209	}
210	new_node->func = func;
211	new_node->name = name;
212#if 0
213	new_node->next = *list;
214	*list = new_node;
215#else
216	prev = NULL;
217	for (p = *list; p != NULL; prev = p, p = p->next) {
218		if (p->order > order) {
219			break;
220		}
221	}
222
223	if (prev == NULL) {
224		new_node->next = *list;
225		*list = new_node;
226	}
227	else {
228		new_node->next = prev->next;
229		prev->next = new_node;
230	}
231#endif
232	splx(pl);
233	return new_node;
234}
235
236void
237apm_hook_delete(apm_hook_func_t *list, apm_hook_func_t delete_node)
238{
239	int pl;
240	apm_hook_func_t p, prev;
241
242	pl = splhigh();
243	prev = NULL;
244	for (p = *list; p != NULL; prev = p, p = p->next) {
245		if (p == delete_node) {
246			goto deleteit;
247		}
248	}
249	panic("Tried to delete unregistered apm_resume_hook.");
250	goto nosuchnode;
251deleteit:
252	if (prev != NULL) {
253		prev->next = p->next;
254	}
255	else {
256		*list = p->next;
257	}
258	free(delete_node, M_DEVBUF);
259nosuchnode:
260	splx(pl);
261}
262
263static struct timeval suspend_time;
264
265/* default APM hook functions */
266static int
267apm_default_resume(void)
268{
269	u_int second, minute, hour;
270	struct timeval resume_time;
271
272	inittodr(0);	/* adjust time to RTC */
273	microtime(&resume_time);
274	second = resume_time.tv_sec - suspend_time.tv_sec;
275	hour = second / 3600;
276	second %= 3600;
277	minute = second / 60;
278	second %= 60;
279	log(LOG_NOTICE, "resumed from suspended mode (slept %02d:%02d:%02d)\n", hour, minute, second);
280	return 0;
281}
282
283static int
284apm_default_suspend(void)
285{
286#if 0
287	int	pl;
288	pl = splhigh();
289	sync(curproc, NULL, NULL);
290	splx(pl);
291#endif
292	microtime(&suspend_time);
293	return 0;
294}
295
296/* list structure for hook */
297static apm_hook_func_t	apm_resume_hook  = NULL;
298static apm_hook_func_t	apm_suspend_hook = NULL;
299
300/* execute resume hook */
301static void
302apm_execute_resume_hook(void)
303{
304	apm_execute_hook(apm_resume_hook);
305}
306
307/* add a node on resume hook */
308apm_hook_func_t
309apm_resume_hook_init(int (*func)(void), char *name, int order)
310{
311	return apm_hook_init(&apm_resume_hook, func, name, order);
312}
313
314/* delete a node from resume hook */
315void
316apm_resume_hook_delete(apm_hook_func_t delete_node)
317{
318	apm_hook_delete(&apm_resume_hook, delete_node);
319}
320
321/* execute suspend hook */
322static void
323apm_execute_suspend_hook(void)
324{
325	apm_execute_hook(apm_suspend_hook);
326}
327
328/* add a node on resume hook */
329apm_hook_func_t
330apm_suspend_hook_init(int (*func)(void), char *name, int order)
331{
332	return apm_hook_init(&apm_suspend_hook, func, name, order);
333}
334
335/* delete a node from resume hook */
336void
337apm_suspend_hook_delete(apm_hook_func_t delete_node)
338{
339	apm_hook_delete(&apm_suspend_hook, delete_node);
340}
341
342/* get APM information */
343static int
344apm_get_info(apm_info_t aip)
345{
346	u_long eax,ebx,ecx;
347
348	eax = (APM_BIOS<<8)|APM_GETPWSTATUS;
349	ebx = PMDV_ALLDEV;
350
351	if (apm_int(&eax,&ebx,&ecx))
352		return 1;
353
354	aip->ai_acline    = (ebx >> 8) & 0xff;
355	aip->ai_batt_stat = ebx & 0xff;
356	aip->ai_batt_life = ecx & 0xff;
357	aip->ai_major     = (u_int)majorversion;
358	aip->ai_minor     = (u_int)minorversion;
359	return 0;
360}
361
362
363/* Define equivalent event sets */
364
365static int equiv_event_num = 0;
366static struct apm_eqv_event equiv_events[APM_MAX_EQUIV_EVENTS];
367
368static int
369apm_def_eqv(apm_eqv_event_t aee)
370{
371	if (equiv_event_num == APM_MAX_EQUIV_EVENTS) {
372		return 1;
373	}
374	memcpy(&equiv_events[equiv_event_num], aee, sizeof(struct apm_eqv_event));
375	equiv_event_num++;
376	return 0;
377}
378
379static void
380apm_flush_eqv(void)
381{
382	equiv_event_num = 0;
383}
384
385static void apm_processevent(void);
386
387/*
388 * Public interface to the suspend/resume:
389 *
390 * Execute suspend and resume hook before and after sleep, respectively.
391 */
392
393void
394apm_suspend_resume(void)
395{
396#ifdef APM_DEBUG
397	printf("Called apm_suspend_resume();\n");
398#endif
399	if (apm_initialized) {
400		apm_execute_suspend_hook();
401		apm_suspend_system();
402		apm_execute_resume_hook();
403		apm_processevent();
404	}
405}
406
407/* inform APM BIOS that CPU is idle */
408void
409apm_cpu_idle(void)
410{
411	if (idle_cpu) {
412		if (active) {
413			__asm ("movw $0x5305, %ax; lcall _apm_addr");
414		}
415	}
416	/*
417	 * Some APM implementation halts CPU in BIOS, whenever
418	 * "CPU-idle" function are invoked, but swtch() of
419	 * FreeBSD halts CPU, therefore, CPU is halted twice
420	 * in the sched loop. It makes the interrupt latency
421	 * terribly long and be able to cause a serious problem
422	 * in interrupt processing. We prevent it by removing
423	 * "hlt" operation from swtch() and managed it under
424	 * APM driver.
425	 */
426	if (!active || halt_cpu) {
427		__asm("sti ; hlt");	/* wait for interrupt */
428	}
429}
430
431/* inform APM BIOS that CPU is busy */
432void
433apm_cpu_busy(void)
434{
435	if (idle_cpu && active) {
436		__asm("movw $0x5306, %ax; lcall _apm_addr");
437	}
438}
439
440
441/*
442 * APM timeout routine:
443 *
444 * This routine is automatically called by timer two times within one
445 * seconed.
446 */
447
448static void
449apm_timeout(void *arg1)
450{
451#ifdef APM_DEBUG
452	printf("t");
453#endif
454	apm_processevent();
455	timeout(apm_timeout, NULL, hz / 2); /* 2 Hz */
456	/* APM driver must polls APM event a time per second */
457}
458
459/* enable APM BIOS */
460static void
461apm_event_enable(void)
462{
463#ifdef APM_DEBUG
464	printf("called apm_event_enable()\n");
465#endif
466	if (apm_initialized) {
467		active = 1;
468		timeout(apm_timeout, NULL, 2 * hz);
469	}
470}
471
472/* disable APM BIOS */
473static void
474apm_event_disable(void)
475{
476#ifdef APM_DEBUG
477	printf("called apm_event_disable()\n");
478#endif
479	if (apm_initialized) {
480		untimeout(apm_timeout, NULL);
481		active = 0;
482	}
483}
484
485/* halt CPU in scheduling loop */
486static void apm_halt_cpu(void)
487{
488	if (apm_initialized) {
489		halt_cpu = 1;
490	}
491}
492
493/* don't halt CPU in scheduling loop */
494static void apm_not_halt_cpu(void)
495{
496	if (apm_initialized) {
497		halt_cpu = 0;
498	}
499}
500
501/* device driver definitions */
502int apmprobe (struct isa_device *);
503int apmattach(struct isa_device *);
504
505struct isa_driver apmdriver = { apmprobe, apmattach, "apm" };
506
507
508/*
509 * probe APM (dummy):
510 *
511 * APM probing routine is placed on locore.s and apm_init.S because
512 * this process forces the CPU to turn to real mode or V86 mode.
513 * Current version uses real mode, but on future version, we want
514 * to use V86 mode in APM initialization.
515 */
516int
517apmprobe(struct isa_device *dvp)
518{
519	switch (apm_version) {
520	case APMINI_CANTFIND:
521		/* silent */
522		return 0;
523	case APMINI_NOT32BIT:
524		printf("apm%d: 32bit connection is not supported.\n", dvp->id_unit);
525		return 0;
526	case APMINI_CONNECTERR:
527		printf("apm%d: 32-bit connection error.\n", dvp->id_unit);
528		return 0;
529	}
530	if ((apm_version & 0xff00) != 0x0100) return 0;
531	if ((apm_version & 0x00f0) >= 0x00a0) return 0;
532	if ((apm_version & 0x000f) >= 0x000a) return 0;
533#ifdef APM_DEBUG
534	printf("sizeof 48bit %d\n",sizeof(struct addr48));
535#endif
536	return -1;
537}
538
539static const char *
540is_enabled(int enabled)
541{
542	if (enabled) {
543		return "enabled";
544	}
545	return "disabled";
546}
547
548
549/* Process APM event */
550static void
551apm_processevent(void)
552{
553	int i, apm_event;
554
555	while (1) {
556		if ((apm_event = apm_getevent()) == PMEV_NOEVENT) {
557			break;
558		}
559#if 1
560#ifdef APM_DEBUG
561#  define OPMEV_DEBUGMESSAGE(symbol) case symbol: \
562		printf("Original APM Event: " #symbol "\n"); break
563#else
564#  define OPMEV_DEBUGMESSAGE(symbol) case symbol: break;
565#endif
566		switch (apm_event) {
567			OPMEV_DEBUGMESSAGE(PMEV_NOEVENT);
568			OPMEV_DEBUGMESSAGE(PMEV_STANDBYREQ);
569			OPMEV_DEBUGMESSAGE(PMEV_SUSPENDREQ);
570			OPMEV_DEBUGMESSAGE(PMEV_NORMRESUME);
571			OPMEV_DEBUGMESSAGE(PMEV_CRITRESUME);
572			OPMEV_DEBUGMESSAGE(PMEV_BATTERYLOW);
573			OPMEV_DEBUGMESSAGE(PMEV_POWERSTATECHANGE);
574			OPMEV_DEBUGMESSAGE(PMEV_UPDATETIME);
575			OPMEV_DEBUGMESSAGE(PMEV_CRITSUSPEND);
576			OPMEV_DEBUGMESSAGE(PMEV_USERSUSPENDREQ);
577			OPMEV_DEBUGMESSAGE(PMEV_STANDBYRESUME);
578			default:
579				printf("Unknown Original APM Event 0x%x\n", apm_event);
580				break;
581		}
582#endif
583		for (i = 0; i < equiv_event_num; i++) {
584			if (equiv_events[i].aee_event == apm_event) {
585				u_int tmp = PMEV_DEFAULT;
586				if (resumed_event) {
587					tmp = equiv_events[i].aee_resume;
588				}
589				else {
590					tmp = equiv_events[i].aee_equiv;
591				}
592				if (tmp != PMEV_DEFAULT) {
593					apm_event = tmp;
594					break;
595				}
596			}
597		}
598#if 1
599#ifdef APM_DEBUG
600#  define PMEV_DEBUGMESSAGE(symbol) case symbol: \
601		printf("APM Event: " #symbol "\n"); break
602#else
603#  define PMEV_DEBUGMESSAGE(symbol) case symbol: break;
604#endif
605		switch (apm_event) {
606			PMEV_DEBUGMESSAGE(PMEV_NOEVENT);
607			PMEV_DEBUGMESSAGE(PMEV_STANDBYREQ);
608			PMEV_DEBUGMESSAGE(PMEV_SUSPENDREQ);
609			PMEV_DEBUGMESSAGE(PMEV_NORMRESUME);
610			PMEV_DEBUGMESSAGE(PMEV_CRITRESUME);
611			PMEV_DEBUGMESSAGE(PMEV_BATTERYLOW);
612			PMEV_DEBUGMESSAGE(PMEV_POWERSTATECHANGE);
613			PMEV_DEBUGMESSAGE(PMEV_UPDATETIME);
614			PMEV_DEBUGMESSAGE(PMEV_CRITSUSPEND);
615			PMEV_DEBUGMESSAGE(PMEV_USERSUSPENDREQ);
616			PMEV_DEBUGMESSAGE(PMEV_STANDBYRESUME);
617			default:
618				printf("Unknown APM Event 0x%x\n", apm_event);
619				break;
620		}
621#endif
622		switch (apm_event) {
623		case PMEV_NOEVENT:
624		case PMEV_STANDBYREQ:
625		case PMEV_POWERSTATECHANGE:
626		case PMEV_CRITSUSPEND:
627		case PMEV_USERSTANDBYREQ:
628		case PMEV_USERSUSPENDREQ:
629			break;
630		case PMEV_BATTERYLOW:
631			apm_battery_low();
632			break;
633		case PMEV_SUSPENDREQ:
634			apm_suspend_resume();
635			break;
636		case PMEV_NORMRESUME:
637		case PMEV_CRITRESUME:
638		case PMEV_UPDATETIME:
639		case PMEV_STANDBYRESUME:
640			inittodr(0);	/* adjust time to RTC */
641			break;
642		}
643	}
644	resumed_event = 0;
645}
646
647
648/*
649 * Attach APM:
650 *
651 * Initialize APM driver (APM BIOS itself has been initialized in locore.s)
652 */
653
654int
655apmattach(struct isa_device *dvp)
656{
657
658	/* setup APM parameters */
659	minorversion = ((apm_version & 0x00f0) >>  4) * 10 + ((apm_version & 0x000f) >> 0);
660	majorversion = ((apm_version & 0xf000) >> 12) * 10 + ((apm_version & 0x0f00) >> 8);
661	intversion = INTVERSION(majorversion, minorversion);
662	cs32_base = (apm_cs32_base << 4) + KERNBASE;
663	cs16_base = (apm_cs16_base << 4) + KERNBASE;
664	ds_base = (apm_ds_base << 4) + KERNBASE;
665	cs_limit = apm_cs_limit;
666	ds_limit = apm_ds_limit;
667	cs_entry = apm_cs_entry;
668	idle_cpu = ((apm_flags & APM_CPUIDLE_SLOW) != 0);
669	disabled = ((apm_flags & APM_DISABLED) != 0);
670	disengaged = ((apm_flags & APM_DISENGAGED) != 0);
671
672	/* print bootstrap messages */
673#ifdef APM_DEBUG
674	printf(" found APM BIOS version %d.%d\n",  majorversion, minorversion);
675	printf("apm%d: Code32 0x%08x, Code16 0x%08x, Data 0x%08x\n",
676		dvp->id_unit, cs32_base, cs16_base, ds_base);
677	printf("apm%d: Code entry 0x%08x, Idling CPU %s, Management %s\n",
678		dvp->id_unit, cs_entry, is_enabled(idle_cpu),
679		is_enabled(!disabled));
680#else /* APM_DEBUG */
681	printf(" found APM BIOS version %d.%d\n", majorversion, minorversion);
682	printf("apm%d: Idling CPU %s\n", dvp->id_unit, is_enabled(idle_cpu));
683#endif /* APM_DEBUG */
684
685	/*
686	 * APM 1.0 does not have:
687	 *
688	 * 	1. segment limit parameters
689	 *
690	 *	2. engage/disengage operations
691	 */
692	if (intversion >= INTVERSION(1, 1)) {
693		printf("apm%d: Engaged control %s\n",
694			dvp->id_unit, is_enabled(!disengaged));
695	}
696	else {
697		cs_limit = 0xffff;
698		ds_limit = 0xffff;
699	}
700
701	/* setup GDT */
702	setup_apm_gdt(cs32_base, cs16_base, ds_base, cs_limit, ds_limit);
703
704	/* setup entry point 48bit pointer */
705	apm_addr.segment = GSEL(GAPMCODE32_SEL, SEL_KPL);
706	apm_addr.offset  = cs_entry;
707
708	/* enable power management */
709	if (disabled) {
710		if (apm_enable_disable_pm(1)) {
711			printf("Warning: APM enable function failed! [%x]\n",
712				apm_errno);
713		}
714	}
715
716	/* engage power managment (APM 1.1 or later) */
717	if (intversion >= INTVERSION(1, 1) && disengaged) {
718		if (apm_engage_disengage_pm(1)) {
719			printf("Warning: APM engage function failed [%x]\n",
720				apm_errno);
721		}
722	}
723
724	apm_suspend_hook_init(apm_default_suspend, "default suspend", APM_MAX_ORDER);
725	apm_resume_hook_init (apm_default_resume , "default resume" , APM_MIN_ORDER);
726	apm_initialized = 1;
727
728	return 0;
729}
730
731int
732apmopen(dev_t dev, int flag, int fmt, struct proc *p)
733{
734	if (!apm_initialized) {
735		return ENXIO;
736	}
737	switch (minor(dev)) {
738	case 0:	/* apm0 */
739		break;
740	defaults:
741		return (ENXIO);
742	}
743	return 0;
744}
745
746int
747apmclose(dev_t dev, int flag, int fmt, struct proc *p)
748{
749	return 0;
750}
751
752int
753apmioctl(dev_t dev, int cmd, caddr_t addr, int flag, struct proc *p)
754{
755	int error = 0;
756	int pl;
757
758#ifdef APM_DEBUG
759	printf("APM ioctl: minor = %d, cmd = 0x%x\n", minor(dev), cmd);
760#endif
761
762	pl = splhigh();
763	if (minor(dev) != 0) {
764		return ENXIO;
765	}
766	if (!apm_initialized) {
767		return ENXIO;
768	}
769	switch (cmd) {
770	case APMIO_SUSPEND:
771		apm_suspend_resume();
772		break;
773	case APMIO_GETINFO:
774		if (apm_get_info((apm_info_t)addr)) {
775			error = ENXIO;
776		}
777		break;
778	case APMIO_DEFEQV:
779		if (apm_def_eqv((apm_eqv_event_t)addr)) {
780			error = ENOSPC;
781		}
782		break;
783	case APMIO_FLUSHEQV:
784		apm_flush_eqv();
785		break;
786	case APMIO_ENABLE:
787		apm_event_enable();
788		break;
789	case APMIO_DISABLE:
790		apm_event_disable();
791		break;
792	case APMIO_HALTCPU:
793		apm_halt_cpu();
794		break;
795	case APMIO_NOTHALTCPU:
796		apm_not_halt_cpu();
797		break;
798	default:
799		error = EINVAL;
800		break;
801	}
802	splx(pl);
803	return error;
804}
805
806#endif /* NAPM > 0 */
807