apm.c revision 50179
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@jp.FreeBSD.org>
6 * Copyright (c) 1996 Nate Williams <nate@FreeBSD.org>
7 * Copyright (c) 1997 Poul-Henning Kamp <phk@FreeBSD.org>
8 *
9 * This software may be used, modified, copied, and distributed, in
10 * both source and binary form provided that the above copyright and
11 * these terms are retained. Under no circumstances is the author
12 * responsible for the proper functioning of this software, nor does
13 * the author assume any responsibility for damages incurred with its
14 * use.
15 *
16 * Sep, 1994	Implemented on FreeBSD 1.1.5.1R (Toshiba AVS001WD)
17 *
18 *	$Id: apm.c,v 1.101 1999/08/22 14:48:00 iwasaki Exp $
19 */
20
21#include "opt_devfs.h"
22
23#include <sys/param.h>
24#include <sys/systm.h>
25#include <sys/eventhandler.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/time.h>
32#include <sys/reboot.h>
33#include <sys/bus.h>
34#include <sys/select.h>
35#include <sys/poll.h>
36#include <sys/fcntl.h>
37#include <sys/proc.h>
38#include <sys/uio.h>
39#include <sys/signalvar.h>
40#include <sys/sysctl.h>
41#include <machine/apm_bios.h>
42#include <machine/segments.h>
43#include <machine/clock.h>
44#include <vm/vm.h>
45#include <vm/vm_param.h>
46#include <vm/pmap.h>
47#include <sys/syslog.h>
48
49#include <machine/pc/bios.h>
50#include <machine/vm86.h>
51
52static int apm_display __P((int newstate));
53static void apm_resume __P((void));
54static int apm_bioscall(void);
55static int apm_check_function_supported __P((u_int version, u_int func));
56
57static u_long	apm_version;
58
59#define APM_NEVENTS 16
60#define APM_NPMEV   13
61
62int	apm_evindex;
63
64/* static data */
65struct apm_softc {
66	int	initialized, active, bios_busy;
67	int	always_halt_cpu, slow_idle_cpu;
68	int	disabled, disengaged;
69	int	standby_countdown, suspend_countdown;
70	u_int	minorversion, majorversion;
71	u_int	intversion, connectmode;
72	u_int	standbys, suspends;
73	struct bios_args bios;
74	struct apmhook sc_suspend;
75	struct apmhook sc_resume;
76	struct selinfo sc_rsel;
77	int	sc_flags;
78	int	event_count;
79	int	event_ptr;
80	struct	apm_event_info event_list[APM_NEVENTS];
81	u_char	event_filter[APM_NPMEV];
82#ifdef DEVFS
83	void 	*sc_devfs_token;
84#endif
85};
86#define	SCFLAG_ONORMAL	0x0000001
87#define	SCFLAG_OCTL	0x0000002
88#define	SCFLAG_OPEN	(SCFLAG_ONORMAL|SCFLAG_OCTL)
89
90#define APMDEV(dev)	(minor(dev)&0x0f)
91#define APMDEV_NORMAL	0
92#define APMDEV_CTL	8
93
94static struct apm_softc apm_softc;
95static struct apmhook	*hook[NAPM_HOOK];		/* XXX */
96
97#define is_enabled(foo) ((foo) ? "enabled" : "disabled")
98
99/* Map version number to integer (keeps ordering of version numbers) */
100#define INTVERSION(major, minor)	((major)*100 + (minor))
101
102static struct callout_handle apm_timeout_ch =
103    CALLOUT_HANDLE_INITIALIZER(&apm_timeout_ch);
104
105static timeout_t apm_timeout;
106static d_open_t apmopen;
107static d_close_t apmclose;
108static d_write_t apmwrite;
109static d_ioctl_t apmioctl;
110static d_poll_t apmpoll;
111
112#define CDEV_MAJOR 39
113static struct cdevsw apm_cdevsw = {
114	/* open */	apmopen,
115	/* close */	apmclose,
116	/* read */	noread,
117	/* write */	apmwrite,
118	/* ioctl */	apmioctl,
119	/* stop */	nostop,
120	/* reset */	noreset,
121	/* devtotty */	nodevtotty,
122	/* poll */	apmpoll,
123	/* mmap */	nommap,
124	/* strategy */	nostrategy,
125	/* name */	"apm",
126	/* parms */	noparms,
127	/* maj */	CDEV_MAJOR,
128	/* dump */	nodump,
129	/* psize */	nopsize,
130	/* flags */	0,
131	/* maxio */	0,
132	/* bmaj */	-1
133};
134
135static int apm_suspend_delay = 1;
136static int apm_standby_delay = 1;
137
138SYSCTL_INT(_machdep, OID_AUTO, apm_suspend_delay, CTLFLAG_RW, &apm_suspend_delay, 1, "");
139SYSCTL_INT(_machdep, OID_AUTO, apm_standby_delay, CTLFLAG_RW, &apm_standby_delay, 1, "");
140
141/*
142 * return  0 if the function successfull,
143 * return  1 if the function unsuccessfull,
144 * return -1 if the function unsupported.
145 */
146static int
147apm_bioscall(void)
148{
149	struct apm_softc *sc = &apm_softc;
150	int errno = 0;
151	u_int apm_func = sc->bios.r.eax & 0xff;
152
153	if (!apm_check_function_supported(sc->intversion, apm_func)) {
154#ifdef APM_DEBUG
155		printf("apm_bioscall: function 0x%x is not supported in v%d.%d\n",
156			apm_func, sc->majorversion, sc->minorversion);
157#endif
158		return (-1);
159	}
160
161	sc->bios_busy = 1;
162	if (sc->connectmode == APM_PROT32CONNECT) {
163		set_bios_selectors(&sc->bios.seg,
164				   BIOSCODE_FLAG | BIOSDATA_FLAG);
165		errno = bios32(&sc->bios.r,
166			       sc->bios.entry, GSEL(GBIOSCODE32_SEL, SEL_KPL));
167	} else {
168		errno = bios16(&sc->bios, NULL);
169	}
170	sc->bios_busy = 0;
171	return (errno);
172}
173
174/* check whether APM function is supported (1)  or not (0). */
175static int
176apm_check_function_supported(u_int version, u_int func)
177{
178	/* except driver version */
179	if (func == APM_DRVVERSION) {
180		return (1);
181	}
182
183	switch (version) {
184	case INTVERSION(1, 0):
185		if (func > APM_GETPMEVENT) {
186			return (0); /* not supported */
187		}
188		break;
189	case INTVERSION(1, 1):
190		if (func > APM_ENGAGEDISENGAGEPM &&
191		    func < APM_OEMFUNC) {
192			return (0); /* not supported */
193		}
194		break;
195	case INTVERSION(1, 2):
196		break;
197	}
198
199	return (1); /* supported */
200}
201
202/* enable/disable power management */
203static int
204apm_enable_disable_pm(int enable)
205{
206	struct apm_softc *sc = &apm_softc;
207
208	sc->bios.r.eax = (APM_BIOS << 8) | APM_ENABLEDISABLEPM;
209
210	if (sc->intversion >= INTVERSION(1, 1))
211		sc->bios.r.ebx  = PMDV_ALLDEV;
212	else
213		sc->bios.r.ebx  = 0xffff;	/* APM version 1.0 only */
214	sc->bios.r.ecx  = enable;
215	sc->bios.r.edx = 0;
216	return (apm_bioscall());
217}
218
219/* register driver version (APM 1.1 or later) */
220static int
221apm_driver_version(int version)
222{
223	struct apm_softc *sc = &apm_softc;
224
225	sc->bios.r.eax = (APM_BIOS << 8) | APM_DRVVERSION;
226	sc->bios.r.ebx  = 0x0;
227	sc->bios.r.ecx  = version;
228	sc->bios.r.edx = 0;
229
230	if (apm_bioscall() == 0 && sc->bios.r.eax == version)
231		return (0);
232
233	/* Some old BIOSes don't return the connection version in %ax. */
234	if (sc->bios.r.eax == ((APM_BIOS << 8) | APM_DRVVERSION))
235		return (0);
236
237	return (1);
238}
239
240/* engage/disengage power management (APM 1.1 or later) */
241static int
242apm_engage_disengage_pm(int engage)
243{
244	struct apm_softc *sc = &apm_softc;
245
246	sc->bios.r.eax = (APM_BIOS << 8) | APM_ENGAGEDISENGAGEPM;
247	sc->bios.r.ebx = PMDV_ALLDEV;
248	sc->bios.r.ecx = engage;
249	sc->bios.r.edx = 0;
250	return (apm_bioscall());
251}
252
253/* get PM event */
254static u_int
255apm_getevent(void)
256{
257	struct apm_softc *sc = &apm_softc;
258
259	sc->bios.r.eax = (APM_BIOS << 8) | APM_GETPMEVENT;
260
261	sc->bios.r.ebx = 0;
262	sc->bios.r.ecx = 0;
263	sc->bios.r.edx = 0;
264	if (apm_bioscall())
265		return (PMEV_NOEVENT);
266	return (sc->bios.r.ebx & 0xffff);
267}
268
269/* suspend entire system */
270static int
271apm_suspend_system(int state)
272{
273	struct apm_softc *sc = &apm_softc;
274
275	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
276	sc->bios.r.ebx = PMDV_ALLDEV;
277	sc->bios.r.ecx = state;
278	sc->bios.r.edx = 0;
279
280	if (apm_bioscall()) {
281 		printf("Entire system suspend failure: errcode = %d\n",
282		       0xff & (sc->bios.r.eax >> 8));
283 		return 1;
284 	}
285 	return 0;
286}
287
288/* Display control */
289/*
290 * Experimental implementation: My laptop machine can't handle this function
291 * If your laptop can control the display via APM, please inform me.
292 *                            HOSOKAWA, Tatsumi <hosokawa@jp.FreeBSD.org>
293 */
294static int
295apm_display(int newstate)
296{
297	struct apm_softc *sc = &apm_softc;
298
299	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
300	sc->bios.r.ebx = PMDV_DISP0;
301	sc->bios.r.ecx = newstate ? PMST_APMENABLED:PMST_SUSPEND;
302	sc->bios.r.edx = 0;
303	if (apm_bioscall()) {
304 		printf("Display off failure: errcode = %d\n",
305		       0xff & (sc->bios.r.eax >> 8));
306 		return 1;
307 	}
308 	return 0;
309}
310
311/*
312 * Turn off the entire system.
313 */
314static void
315apm_power_off(void *junk, int howto)
316{
317	struct apm_softc *sc = &apm_softc;
318
319	/* Not halting powering off, or not active */
320	if (!(howto & RB_POWEROFF) || !apm_softc.active)
321		return;
322	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
323	sc->bios.r.ebx = PMDV_ALLDEV;
324	sc->bios.r.ecx = PMST_OFF;
325	sc->bios.r.edx = 0;
326	(void) apm_bioscall();
327}
328
329/* APM Battery low handler */
330static void
331apm_battery_low(void)
332{
333	printf("\007\007 * * * BATTERY IS LOW * * * \007\007");
334}
335
336/* APM hook manager */
337static struct apmhook *
338apm_add_hook(struct apmhook **list, struct apmhook *ah)
339{
340	int s;
341	struct apmhook *p, *prev;
342
343#ifdef APM_DEBUG
344	printf("Add hook \"%s\"\n", ah->ah_name);
345#endif
346
347	s = splhigh();
348	if (ah == NULL)
349		panic("illegal apm_hook!");
350	prev = NULL;
351	for (p = *list; p != NULL; prev = p, p = p->ah_next)
352		if (p->ah_order > ah->ah_order)
353			break;
354
355	if (prev == NULL) {
356		ah->ah_next = *list;
357		*list = ah;
358	} else {
359		ah->ah_next = prev->ah_next;
360		prev->ah_next = ah;
361	}
362	splx(s);
363	return ah;
364}
365
366static void
367apm_del_hook(struct apmhook **list, struct apmhook *ah)
368{
369	int s;
370	struct apmhook *p, *prev;
371
372	s = splhigh();
373	prev = NULL;
374	for (p = *list; p != NULL; prev = p, p = p->ah_next)
375		if (p == ah)
376			goto deleteit;
377	panic("Tried to delete unregistered apm_hook.");
378	goto nosuchnode;
379deleteit:
380	if (prev != NULL)
381		prev->ah_next = p->ah_next;
382	else
383		*list = p->ah_next;
384nosuchnode:
385	splx(s);
386}
387
388
389/* APM driver calls some functions automatically */
390static void
391apm_execute_hook(struct apmhook *list)
392{
393	struct apmhook *p;
394
395	for (p = list; p != NULL; p = p->ah_next) {
396#ifdef APM_DEBUG
397		printf("Execute APM hook \"%s.\"\n", p->ah_name);
398#endif
399		if ((*(p->ah_fun))(p->ah_arg))
400			printf("Warning: APM hook \"%s\" failed", p->ah_name);
401	}
402}
403
404
405/* establish an apm hook */
406struct apmhook *
407apm_hook_establish(int apmh, struct apmhook *ah)
408{
409	if (apmh < 0 || apmh >= NAPM_HOOK)
410		return NULL;
411
412	return apm_add_hook(&hook[apmh], ah);
413}
414
415/* disestablish an apm hook */
416void
417apm_hook_disestablish(int apmh, struct apmhook *ah)
418{
419	if (apmh < 0 || apmh >= NAPM_HOOK)
420		return;
421
422	apm_del_hook(&hook[apmh], ah);
423}
424
425
426static struct timeval suspend_time;
427static struct timeval diff_time;
428
429static int
430apm_default_resume(void *arg)
431{
432	int pl;
433	u_int second, minute, hour;
434	struct timeval resume_time, tmp_time;
435
436	/* modified for adjkerntz */
437	pl = splsoftclock();
438	inittodr(0);			/* adjust time to RTC */
439	microtime(&resume_time);
440	getmicrotime(&tmp_time);
441	timevaladd(&tmp_time, &diff_time);
442
443#ifdef FIXME
444	/* XXX THIS DOESN'T WORK!!! */
445	time = tmp_time;
446#endif
447
448#ifdef APM_FIXUP_CALLTODO
449	/* Calculate the delta time suspended */
450	timevalsub(&resume_time, &suspend_time);
451	/* Fixup the calltodo list with the delta time. */
452	adjust_timeout_calltodo(&resume_time);
453#endif /* APM_FIXUP_CALLTODOK */
454	splx(pl);
455#ifndef APM_FIXUP_CALLTODO
456	second = resume_time.tv_sec - suspend_time.tv_sec;
457#else /* APM_FIXUP_CALLTODO */
458	/*
459	 * We've already calculated resume_time to be the delta between
460	 * the suspend and the resume.
461	 */
462	second = resume_time.tv_sec;
463#endif /* APM_FIXUP_CALLTODO */
464	hour = second / 3600;
465	second %= 3600;
466	minute = second / 60;
467	second %= 60;
468	log(LOG_NOTICE, "resumed from suspended mode (slept %02d:%02d:%02d)\n",
469		hour, minute, second);
470	return 0;
471}
472
473static int
474apm_default_suspend(void *arg)
475{
476	int	pl;
477
478	pl = splsoftclock();
479	microtime(&diff_time);
480	inittodr(0);
481	microtime(&suspend_time);
482	timevalsub(&diff_time, &suspend_time);
483	splx(pl);
484	return 0;
485}
486
487static int apm_record_event __P((struct apm_softc *, u_int));
488static void apm_processevent(void);
489
490static u_int apm_op_inprog = 0;
491
492static void
493apm_do_suspend(void)
494{
495	struct apm_softc *sc = &apm_softc;
496	int error;
497
498	if (!sc)
499		return;
500
501	apm_op_inprog = 0;
502	sc->suspends = sc->suspend_countdown = 0;
503
504	if (sc->initialized) {
505		error = DEVICE_SUSPEND(root_bus);
506		/*
507		 * XXX Shouldn't ignore the error like this, but should
508		 * instead fix the newbus code.  Until that happens,
509		 * I'm doing this to get suspend working again.
510		 */
511		if (error)
512			printf("DEVICE_SUSPEND error %d, ignored\n", error);
513		apm_execute_hook(hook[APM_HOOK_SUSPEND]);
514		if (apm_suspend_system(PMST_SUSPEND) == 0)
515			apm_processevent();
516		else
517			/* Failure, 'resume' the system again */
518			apm_execute_hook(hook[APM_HOOK_RESUME]);
519	}
520}
521
522static void
523apm_do_standby(void)
524{
525	struct apm_softc *sc = &apm_softc;
526
527	if (!sc)
528		return;
529
530	apm_op_inprog = 0;
531	sc->standbys = sc->standby_countdown = 0;
532
533	if (sc->initialized) {
534		/*
535		 * As far as standby, we don't need to execute
536		 * all of suspend hooks.
537		 */
538		apm_default_suspend(&apm_softc);
539		if (apm_suspend_system(PMST_STANDBY) == 0)
540			apm_processevent();
541	}
542}
543
544static void
545apm_lastreq_notify(void)
546{
547	struct apm_softc *sc = &apm_softc;
548
549	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
550	sc->bios.r.ebx = PMDV_ALLDEV;
551	sc->bios.r.ecx = PMST_LASTREQNOTIFY;
552	sc->bios.r.edx = 0;
553	apm_bioscall();
554}
555
556static int
557apm_lastreq_rejected(void)
558{
559	struct apm_softc *sc = &apm_softc;
560
561	if (apm_op_inprog == 0) {
562		return 1;	/* no operation in progress */
563	}
564
565	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
566	sc->bios.r.ebx = PMDV_ALLDEV;
567	sc->bios.r.ecx = PMST_LASTREQREJECT;
568	sc->bios.r.edx = 0;
569
570	if (apm_bioscall()) {
571#ifdef APM_DEBUG
572		printf("apm_lastreq_rejected: failed\n");
573#endif
574		return 1;
575	}
576	apm_op_inprog = 0;
577	return 0;
578}
579
580/*
581 * Public interface to the suspend/resume:
582 *
583 * Execute suspend and resume hook before and after sleep, respectively.
584 *
585 */
586
587void
588apm_suspend(int state)
589{
590	struct apm_softc *sc = &apm_softc;
591
592	switch (state) {
593	case PMST_SUSPEND:
594		if (sc->suspends)
595			return;
596		sc->suspends++;
597		sc->suspend_countdown = apm_suspend_delay;
598		break;
599	case PMST_STANDBY:
600		if (sc->standbys)
601			return;
602		sc->standbys++;
603		sc->standby_countdown = apm_standby_delay;
604		break;
605	default:
606		printf("apm_suspend: Unknown Suspend state 0x%x\n", state);
607		return;
608	}
609
610	apm_op_inprog++;
611	apm_lastreq_notify();
612}
613
614void
615apm_resume(void)
616{
617	struct apm_softc *sc = &apm_softc;
618
619	if (!sc)
620		return;
621
622	if (sc->initialized) {
623		DEVICE_RESUME(root_bus);
624		apm_execute_hook(hook[APM_HOOK_RESUME]);
625	}
626}
627
628
629/* get APM information */
630static int
631apm_get_info(apm_info_t aip)
632{
633	struct apm_softc *sc = &apm_softc;
634
635	sc->bios.r.eax = (APM_BIOS << 8) | APM_GETPWSTATUS;
636	sc->bios.r.ebx = PMDV_ALLDEV;
637	sc->bios.r.ecx = 0;
638	sc->bios.r.edx = 0xffff;		/* default to unknown battery time */
639
640	if (apm_bioscall())
641		return 1;
642
643	aip->ai_infoversion = 1;
644	aip->ai_acline      = (sc->bios.r.ebx >> 8) & 0xff;
645	aip->ai_batt_stat   = sc->bios.r.ebx & 0xff;
646	aip->ai_batt_life   = sc->bios.r.ecx & 0xff;
647	aip->ai_major       = (u_int)sc->majorversion;
648	aip->ai_minor       = (u_int)sc->minorversion;
649	aip->ai_status      = (u_int)sc->active;
650	sc->bios.r.edx &= 0xffff;
651	if (sc->bios.r.edx == 0xffff)	/* Time is unknown */
652		aip->ai_batt_time = -1;
653	else if (sc->bios.r.edx & 0x8000)	/* Time is in minutes */
654		aip->ai_batt_time = (sc->bios.r.edx & 0x7fff) * 60;
655	else			/* Time is in seconds */
656		aip->ai_batt_time = sc->bios.r.edx;
657
658	sc->bios.r.eax = (APM_BIOS << 8) | APM_GETCAPABILITIES;
659	sc->bios.r.ebx = 0;
660	sc->bios.r.ecx = 0;
661	sc->bios.r.edx = 0;
662	if (apm_bioscall()) {
663		aip->ai_batteries = -1;	/* Unknown */
664		aip->ai_capabilities = 0xff00; /* Unknown, with no bits set */
665	} else {
666		aip->ai_batteries = sc->bios.r.ebx & 0xff;
667		aip->ai_capabilities = sc->bios.r.ecx & 0xf;
668	}
669
670	bzero(aip->ai_spare, sizeof aip->ai_spare);
671
672	return 0;
673}
674
675
676/* inform APM BIOS that CPU is idle */
677void
678apm_cpu_idle(void)
679{
680	struct apm_softc *sc = &apm_softc;
681
682	if (sc->active) {
683
684		sc->bios.r.eax = (APM_BIOS <<8) | APM_CPUIDLE;
685		sc->bios.r.edx = sc->bios.r.ecx = sc->bios.r.ebx = 0;
686		(void) apm_bioscall();
687	}
688	/*
689	 * Some APM implementation halts CPU in BIOS, whenever
690	 * "CPU-idle" function are invoked, but swtch() of
691	 * FreeBSD halts CPU, therefore, CPU is halted twice
692	 * in the sched loop. It makes the interrupt latency
693	 * terribly long and be able to cause a serious problem
694	 * in interrupt processing. We prevent it by removing
695	 * "hlt" operation from swtch() and managed it under
696	 * APM driver.
697	 */
698	if (!sc->active || sc->always_halt_cpu)
699		__asm("hlt");	/* wait for interrupt */
700}
701
702/* inform APM BIOS that CPU is busy */
703void
704apm_cpu_busy(void)
705{
706	struct apm_softc *sc = &apm_softc;
707
708	/*
709	 * The APM specification says this is only necessary if your BIOS
710	 * slows down the processor in the idle task, otherwise it's not
711	 * necessary.
712	 */
713	if (sc->slow_idle_cpu && sc->active) {
714
715		sc->bios.r.eax = (APM_BIOS <<8) | APM_CPUBUSY;
716		sc->bios.r.edx = sc->bios.r.ecx = sc->bios.r.ebx = 0;
717		apm_bioscall();
718	}
719}
720
721
722/*
723 * APM timeout routine:
724 *
725 * This routine is automatically called by timer once per second.
726 */
727
728static void
729apm_timeout(void *dummy)
730{
731	struct apm_softc *sc = &apm_softc;
732
733	if (apm_op_inprog)
734		apm_lastreq_notify();
735
736	if (sc->standbys && sc->standby_countdown-- <= 0)
737		apm_do_standby();
738
739	if (sc->suspends && sc->suspend_countdown-- <= 0)
740		apm_do_suspend();
741
742	if (!sc->bios_busy)
743		apm_processevent();
744
745	if (sc->active == 1)
746		/* Run slightly more oftan than 1 Hz */
747		apm_timeout_ch = timeout(apm_timeout, NULL, hz - 1 );
748}
749
750/* enable APM BIOS */
751static void
752apm_event_enable(void)
753{
754	struct apm_softc *sc = &apm_softc;
755
756#ifdef APM_DEBUG
757	printf("called apm_event_enable()\n");
758#endif
759	if (sc->initialized) {
760		sc->active = 1;
761		apm_timeout(sc);
762	}
763}
764
765/* disable APM BIOS */
766static void
767apm_event_disable(void)
768{
769	struct apm_softc *sc = &apm_softc;
770
771#ifdef APM_DEBUG
772	printf("called apm_event_disable()\n");
773#endif
774	if (sc->initialized) {
775		untimeout(apm_timeout, NULL, apm_timeout_ch);
776		sc->active = 0;
777	}
778}
779
780/* halt CPU in scheduling loop */
781static void
782apm_halt_cpu(void)
783{
784	struct apm_softc *sc = &apm_softc;
785
786	if (sc->initialized)
787		sc->always_halt_cpu = 1;
788}
789
790/* don't halt CPU in scheduling loop */
791static void
792apm_not_halt_cpu(void)
793{
794	struct apm_softc *sc = &apm_softc;
795
796	if (sc->initialized)
797		sc->always_halt_cpu = 0;
798}
799
800/* device driver definitions */
801
802/*
803 * Create "connection point"
804 */
805static void
806apm_identify(driver_t *driver, device_t parent)
807{
808	device_t child;
809
810	child = BUS_ADD_CHILD(parent, 0, "apm", 0);
811	if (child == NULL)
812		panic("apm_identify");
813}
814
815/*
816 * probe for APM BIOS
817 */
818static int
819apm_probe(device_t dev)
820{
821#define APM_KERNBASE	KERNBASE
822	struct vm86frame	vmf;
823	struct apm_softc	*sc = &apm_softc;
824	int			disabled, flags;
825
826	if (resource_int_value("apm", 0, "disabled", &disabled) == 0
827	    && disabled != 0)
828		return ENXIO;
829
830	device_set_desc(dev, "APM BIOS");
831
832	if ( device_get_unit(dev) > 0 ) {
833		printf("apm: Only one APM driver supported.\n");
834		return ENXIO;
835	}
836
837	if (resource_int_value("apm", 0, "flags", &flags) != 0)
838		flags = 0;
839
840	bzero(&vmf, sizeof(struct vm86frame));		/* safety */
841	bzero(&apm_softc, sizeof(apm_softc));
842	vmf.vmf_ah = APM_BIOS;
843	vmf.vmf_al = APM_INSTCHECK;
844	vmf.vmf_bx = 0;
845	if (vm86_intcall(APM_INT, &vmf))
846		return ENXIO;			/* APM not found */
847	if (vmf.vmf_bx != 0x504d) {
848		printf("apm: incorrect signature (0x%x)\n", vmf.vmf_bx);
849		return ENXIO;
850	}
851	if ((vmf.vmf_cx & (APM_32BIT_SUPPORT | APM_16BIT_SUPPORT)) == 0) {
852		printf("apm: protected mode connections are not supported\n");
853		return ENXIO;
854	}
855
856	apm_version = vmf.vmf_ax;
857	sc->slow_idle_cpu = ((vmf.vmf_cx & APM_CPUIDLE_SLOW) != 0);
858	sc->disabled = ((vmf.vmf_cx & APM_DISABLED) != 0);
859	sc->disengaged = ((vmf.vmf_cx & APM_DISENGAGED) != 0);
860
861	vmf.vmf_ah = APM_BIOS;
862	vmf.vmf_al = APM_DISCONNECT;
863	vmf.vmf_bx = 0;
864        vm86_intcall(APM_INT, &vmf);		/* disconnect, just in case */
865
866	if ((vmf.vmf_cx & APM_32BIT_SUPPORT) != 0) {
867		vmf.vmf_ah = APM_BIOS;
868		vmf.vmf_al = APM_PROT32CONNECT;
869		vmf.vmf_bx = 0;
870		if (vm86_intcall(APM_INT, &vmf)) {
871			printf("apm: 32-bit connection error.\n");
872			return (ENXIO);
873 		}
874		sc->bios.seg.code32.base = (vmf.vmf_ax << 4) + APM_KERNBASE;
875		sc->bios.seg.code32.limit = 0xffff;
876		sc->bios.seg.code16.base = (vmf.vmf_cx << 4) + APM_KERNBASE;
877		sc->bios.seg.code16.limit = 0xffff;
878		sc->bios.seg.data.base = (vmf.vmf_dx << 4) + APM_KERNBASE;
879		sc->bios.seg.data.limit = 0xffff;
880		sc->bios.entry = vmf.vmf_ebx;
881		sc->connectmode = APM_PROT32CONNECT;
882 	} else {
883		/* use 16-bit connection */
884		vmf.vmf_ah = APM_BIOS;
885		vmf.vmf_al = APM_PROT16CONNECT;
886		vmf.vmf_bx = 0;
887		if (vm86_intcall(APM_INT, &vmf)) {
888			printf("apm: 16-bit connection error.\n");
889			return (ENXIO);
890		}
891		sc->bios.seg.code16.base = (vmf.vmf_ax << 4) + APM_KERNBASE;
892		sc->bios.seg.code16.limit = 0xffff;
893		sc->bios.seg.data.base = (vmf.vmf_cx << 4) + APM_KERNBASE;
894		sc->bios.seg.data.limit = 0xffff;
895		sc->bios.entry = vmf.vmf_bx;
896		sc->connectmode = APM_PROT16CONNECT;
897	}
898	return(0);
899}
900
901
902/*
903 * return 0 if the user will notice and handle the event,
904 * return 1 if the kernel driver should do so.
905 */
906static int
907apm_record_event(struct apm_softc *sc, u_int event_type)
908{
909	struct apm_event_info *evp;
910
911	if ((sc->sc_flags & SCFLAG_OPEN) == 0)
912		return 1;		/* no user waiting */
913	if (sc->event_count == APM_NEVENTS)
914		return 1;			/* overflow */
915	if (sc->event_filter[event_type] == 0)
916		return 1;		/* not registered */
917	evp = &sc->event_list[sc->event_ptr];
918	sc->event_count++;
919	sc->event_ptr++;
920	sc->event_ptr %= APM_NEVENTS;
921	evp->type = event_type;
922	evp->index = ++apm_evindex;
923	selwakeup(&sc->sc_rsel);
924	return (sc->sc_flags & SCFLAG_OCTL) ? 0 : 1; /* user may handle */
925}
926
927/* Process APM event */
928static void
929apm_processevent(void)
930{
931	int apm_event;
932	struct apm_softc *sc = &apm_softc;
933
934#ifdef APM_DEBUG
935#  define OPMEV_DEBUGMESSAGE(symbol) case symbol: \
936	printf("Received APM Event: " #symbol "\n");
937#else
938#  define OPMEV_DEBUGMESSAGE(symbol) case symbol:
939#endif
940	do {
941		apm_event = apm_getevent();
942		switch (apm_event) {
943		    OPMEV_DEBUGMESSAGE(PMEV_STANDBYREQ);
944			if (apm_op_inprog == 0) {
945			    apm_op_inprog++;
946			    if (apm_record_event(sc, apm_event)) {
947				apm_suspend(PMST_STANDBY);
948			    }
949			}
950			break;
951		    OPMEV_DEBUGMESSAGE(PMEV_SUSPENDREQ);
952 			apm_lastreq_notify();
953			if (apm_op_inprog == 0) {
954			    apm_op_inprog++;
955			    if (apm_record_event(sc, apm_event)) {
956				apm_do_suspend();
957			    }
958			}
959			return; /* XXX skip the rest */
960		    OPMEV_DEBUGMESSAGE(PMEV_USERSUSPENDREQ);
961 			apm_lastreq_notify();
962			if (apm_op_inprog == 0) {
963			    apm_op_inprog++;
964			    if (apm_record_event(sc, apm_event)) {
965				apm_do_suspend();
966			    }
967			}
968			return; /* XXX skip the rest */
969		    OPMEV_DEBUGMESSAGE(PMEV_CRITSUSPEND);
970			apm_do_suspend();
971			break;
972		    OPMEV_DEBUGMESSAGE(PMEV_NORMRESUME);
973			apm_record_event(sc, apm_event);
974			apm_resume();
975			break;
976		    OPMEV_DEBUGMESSAGE(PMEV_CRITRESUME);
977			apm_record_event(sc, apm_event);
978			apm_resume();
979			break;
980		    OPMEV_DEBUGMESSAGE(PMEV_STANDBYRESUME);
981			apm_record_event(sc, apm_event);
982			apm_resume();
983			break;
984		    OPMEV_DEBUGMESSAGE(PMEV_BATTERYLOW);
985			if (apm_record_event(sc, apm_event)) {
986			    apm_battery_low();
987			    apm_suspend(PMST_SUSPEND);
988			}
989			break;
990		    OPMEV_DEBUGMESSAGE(PMEV_POWERSTATECHANGE);
991			apm_record_event(sc, apm_event);
992			break;
993		    OPMEV_DEBUGMESSAGE(PMEV_UPDATETIME);
994			apm_record_event(sc, apm_event);
995			inittodr(0);	/* adjust time to RTC */
996			break;
997		    case PMEV_NOEVENT:
998			break;
999		    default:
1000			printf("Unknown Original APM Event 0x%x\n", apm_event);
1001			    break;
1002		}
1003	} while (apm_event != PMEV_NOEVENT);
1004}
1005
1006/*
1007 * Attach APM:
1008 *
1009 * Initialize APM driver
1010 */
1011
1012static int
1013apm_attach(device_t dev)
1014{
1015	struct apm_softc	*sc = &apm_softc;
1016	int			flags;
1017	int			drv_version;
1018
1019	if (resource_int_value("apm", 0, "flags", &flags) != 0)
1020		flags = 0;
1021
1022	if (flags & 0x20)
1023		statclock_disable = 1;
1024
1025	sc->initialized = 0;
1026
1027	/* Must be externally enabled */
1028	sc->active = 0;
1029
1030	/* Always call HLT in idle loop */
1031	sc->always_halt_cpu = 1;
1032
1033	/* print bootstrap messages */
1034#ifdef APM_DEBUG
1035	printf("apm: APM BIOS version %04x\n",  apm_version);
1036	printf("apm: Code16 0x%08x, Data 0x%08x\n",
1037               sc->bios.seg.code16.base, sc->bios.seg.data.base);
1038	printf("apm: Code entry 0x%08x, Idling CPU %s, Management %s\n",
1039               sc->bios.entry, is_enabled(sc->slow_idle_cpu),
1040	       is_enabled(!sc->disabled));
1041	printf("apm: CS_limit=0x%x, DS_limit=0x%x\n",
1042	      sc->bios.seg.code16.limit, sc->bios.seg.data.limit);
1043#endif /* APM_DEBUG */
1044
1045#if 0
1046	/*
1047	 * XXX this may not be needed anymore
1048	 */
1049	if ((flags & 0x10)) {
1050		if ((flags & 0xf) >= 0x2) {
1051			apm_driver_version(0x102);
1052		}
1053		if (!apm_version && (flags & 0xf) >= 0x1) {
1054			apm_driver_version(0x101);
1055		}
1056	} else {
1057		apm_driver_version(0x102);
1058		if (!apm_version)
1059			apm_driver_version(0x101);
1060	}
1061#endif
1062	/*
1063        * In one test, apm bios version was 1.02; an attempt to register
1064        * a 1.04 driver resulted in a 1.00 connection!  Registering a
1065        * 1.02 driver resulted in a 1.02 connection.
1066        */
1067	drv_version = apm_version > 0x102 ? 0x102 : apm_version;
1068	for (; drv_version > 0x100; drv_version--)
1069		if (apm_driver_version(drv_version) == 0)
1070			break;
1071	sc->minorversion = ((drv_version & 0x00f0) >>  4) * 10 +
1072		((drv_version & 0x000f) >> 0);
1073	sc->majorversion = ((drv_version & 0xf000) >> 12) * 10 +
1074		((apm_version & 0x0f00) >> 8);
1075
1076	sc->intversion = INTVERSION(sc->majorversion, sc->minorversion);
1077
1078#ifdef APM_DEBUG
1079	if (sc->intversion >= INTVERSION(1, 1))
1080		printf("apm: Engaged control %s\n", is_enabled(!sc->disengaged));
1081#endif
1082
1083	printf("apm: found APM BIOS v%ld.%ld, connected at v%d.%d\n",
1084	       ((apm_version & 0xf000) >> 12) * 10 + ((apm_version & 0x0f00) >> 8),
1085	       ((apm_version & 0x00f0) >> 4) * 10 + ((apm_version & 0x000f) >> 0),
1086	       sc->majorversion, sc->minorversion);
1087
1088#ifdef APM_DEBUG
1089	printf("apm: Slow Idling CPU %s\n", is_enabled(sc->slow_idle_cpu));
1090#endif
1091
1092	/* enable power management */
1093	if (sc->disabled) {
1094		if (apm_enable_disable_pm(1)) {
1095#ifdef APM_DEBUG
1096			printf("apm: *Warning* enable function failed! [%x]\n",
1097				(sc->bios.r.eax >> 8) & 0xff);
1098#endif
1099		}
1100	}
1101
1102	/* engage power managment (APM 1.1 or later) */
1103	if (sc->intversion >= INTVERSION(1, 1) && sc->disengaged) {
1104		if (apm_engage_disengage_pm(1)) {
1105#ifdef APM_DEBUG
1106			printf("apm: *Warning* engage function failed err=[%x]",
1107				(sc->bios.r.eax >> 8) & 0xff);
1108			printf(" (Docked or using external power?).\n");
1109#endif
1110		}
1111	}
1112
1113        /* default suspend hook */
1114        sc->sc_suspend.ah_fun = apm_default_suspend;
1115        sc->sc_suspend.ah_arg = sc;
1116        sc->sc_suspend.ah_name = "default suspend";
1117        sc->sc_suspend.ah_order = APM_MAX_ORDER;
1118
1119        /* default resume hook */
1120        sc->sc_resume.ah_fun = apm_default_resume;
1121        sc->sc_resume.ah_arg = sc;
1122        sc->sc_resume.ah_name = "default resume";
1123        sc->sc_resume.ah_order = APM_MIN_ORDER;
1124
1125        apm_hook_establish(APM_HOOK_SUSPEND, &sc->sc_suspend);
1126        apm_hook_establish(APM_HOOK_RESUME , &sc->sc_resume);
1127
1128	/* Power the system off using APM */
1129	EVENTHANDLER_REGISTER(shutdown_final, apm_power_off, NULL,
1130			      SHUTDOWN_PRI_LAST);
1131
1132	sc->initialized = 1;
1133
1134#ifdef DEVFS
1135	sc->sc_devfs_token =
1136		devfs_add_devswf(&apm_cdevsw, 0, DV_CHR, 0, 0, 0600, "apm");
1137#endif
1138	return 0;
1139}
1140
1141static int
1142apmopen(dev_t dev, int flag, int fmt, struct proc *p)
1143{
1144	struct apm_softc *sc = &apm_softc;
1145	int ctl = APMDEV(dev);
1146
1147	if (!sc->initialized)
1148		return (ENXIO);
1149
1150	switch (ctl) {
1151	case APMDEV_CTL:
1152		if (!(flag & FWRITE))
1153			return EINVAL;
1154		if (sc->sc_flags & SCFLAG_OCTL)
1155			return EBUSY;
1156		sc->sc_flags |= SCFLAG_OCTL;
1157		bzero(sc->event_filter, sizeof sc->event_filter);
1158		break;
1159	case APMDEV_NORMAL:
1160		sc->sc_flags |= SCFLAG_ONORMAL;
1161		break;
1162	default:
1163		return ENXIO;
1164		break;
1165	}
1166	return 0;
1167}
1168
1169static int
1170apmclose(dev_t dev, int flag, int fmt, struct proc *p)
1171{
1172	struct apm_softc *sc = &apm_softc;
1173	int ctl = APMDEV(dev);
1174
1175	switch (ctl) {
1176	case APMDEV_CTL:
1177		apm_lastreq_rejected();
1178		sc->sc_flags &= ~SCFLAG_OCTL;
1179		bzero(sc->event_filter, sizeof sc->event_filter);
1180		break;
1181	case APMDEV_NORMAL:
1182		sc->sc_flags &= ~SCFLAG_ONORMAL;
1183		break;
1184	}
1185	if ((sc->sc_flags & SCFLAG_OPEN) == 0) {
1186		sc->event_count = 0;
1187		sc->event_ptr = 0;
1188	}
1189	return 0;
1190}
1191
1192static int
1193apmioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct proc *p)
1194{
1195	struct apm_softc *sc = &apm_softc;
1196	struct apm_bios_arg *args;
1197	int error = 0;
1198	int ret;
1199	int newstate;
1200
1201	if (!sc->initialized)
1202		return (ENXIO);
1203#ifdef APM_DEBUG
1204	printf("APM ioctl: cmd = 0x%x\n", cmd);
1205#endif
1206	switch (cmd) {
1207	case APMIO_SUSPEND:
1208		if (sc->active)
1209			apm_suspend(PMST_SUSPEND);
1210		else
1211			error = EINVAL;
1212		break;
1213
1214	case APMIO_STANDBY:
1215		if (sc->active)
1216			apm_suspend(PMST_STANDBY);
1217		else
1218			error = EINVAL;
1219		break;
1220
1221	case APMIO_GETINFO_OLD:
1222		{
1223			struct apm_info info;
1224			apm_info_old_t aiop;
1225
1226			if (apm_get_info(&info))
1227				error = ENXIO;
1228			aiop = (apm_info_old_t)addr;
1229			aiop->ai_major = info.ai_major;
1230			aiop->ai_minor = info.ai_minor;
1231			aiop->ai_acline = info.ai_acline;
1232			aiop->ai_batt_stat = info.ai_batt_stat;
1233			aiop->ai_batt_life = info.ai_batt_life;
1234			aiop->ai_status = info.ai_status;
1235		}
1236		break;
1237	case APMIO_GETINFO:
1238		if (apm_get_info((apm_info_t)addr))
1239			error = ENXIO;
1240		break;
1241	case APMIO_ENABLE:
1242		apm_event_enable();
1243		break;
1244	case APMIO_DISABLE:
1245		apm_event_disable();
1246		break;
1247	case APMIO_HALTCPU:
1248		apm_halt_cpu();
1249		break;
1250	case APMIO_NOTHALTCPU:
1251		apm_not_halt_cpu();
1252		break;
1253	case APMIO_DISPLAY:
1254		newstate = *(int *)addr;
1255		if (apm_display(newstate))
1256			error = ENXIO;
1257		break;
1258	case APMIO_BIOS:
1259		/* XXX compatibility with the old interface */
1260		args = (struct apm_bios_arg *)addr;
1261		sc->bios.r.eax = args->eax;
1262		sc->bios.r.ebx = args->ebx;
1263		sc->bios.r.ecx = args->ecx;
1264		sc->bios.r.edx = args->edx;
1265		sc->bios.r.esi = args->esi;
1266		sc->bios.r.edi = args->edi;
1267		if ((ret = apm_bioscall())) {
1268			/*
1269			 * Return code 1 means bios call was unsuccessful.
1270			 * Error code is stored in %ah.
1271			 * Return code -1 means bios call was unsupported
1272			 * in the APM BIOS version.
1273			 */
1274			if (ret == -1) {
1275				error = EINVAL;
1276			}
1277		} else {
1278			/*
1279			 * Return code 0 means bios call was successful.
1280			 * We need only %al and can discard %ah.
1281			 */
1282			sc->bios.r.eax &= 0xff;
1283		}
1284		args->eax = sc->bios.r.eax;
1285		args->ebx = sc->bios.r.ebx;
1286		args->ecx = sc->bios.r.ecx;
1287		args->edx = sc->bios.r.edx;
1288		args->esi = sc->bios.r.esi;
1289		args->edi = sc->bios.r.edi;
1290		break;
1291	default:
1292		error = EINVAL;
1293		break;
1294	}
1295
1296	/* for /dev/apmctl */
1297	if (APMDEV(dev) == APMDEV_CTL) {
1298		struct apm_event_info *evp;
1299		int i;
1300
1301		error = 0;
1302		switch (cmd) {
1303		case APMIO_NEXTEVENT:
1304			if (!sc->event_count) {
1305				error = EAGAIN;
1306			} else {
1307				evp = (struct apm_event_info *)addr;
1308				i = sc->event_ptr + APM_NEVENTS - sc->event_count;
1309				i %= APM_NEVENTS;
1310				*evp = sc->event_list[i];
1311				sc->event_count--;
1312			}
1313			break;
1314		case APMIO_REJECTLASTREQ:
1315			if (apm_lastreq_rejected()) {
1316				error = EINVAL;
1317			}
1318			break;
1319		default:
1320			error = EINVAL;
1321			break;
1322		}
1323	}
1324
1325	return error;
1326}
1327
1328static int
1329apmwrite(dev_t dev, struct uio *uio, int ioflag)
1330{
1331	struct apm_softc *sc = &apm_softc;
1332	u_int event_type;
1333	int error;
1334	u_char enabled;
1335
1336	if (APMDEV(dev) != APMDEV_CTL)
1337		return(ENODEV);
1338	if (uio->uio_resid != sizeof(u_int))
1339		return(E2BIG);
1340
1341	if ((error = uiomove((caddr_t)&event_type, sizeof(u_int), uio)))
1342		return(error);
1343
1344	if (event_type < 0 || event_type >= APM_NPMEV)
1345		return(EINVAL);
1346
1347	if (sc->event_filter[event_type] == 0) {
1348		enabled = 1;
1349	} else {
1350		enabled = 0;
1351	}
1352	sc->event_filter[event_type] = enabled;
1353#ifdef APM_DEBUG
1354	printf("apmwrite: event 0x%x %s\n", event_type, is_enabled(enabled));
1355#endif
1356
1357	return uio->uio_resid;
1358}
1359
1360static int
1361apmpoll(dev_t dev, int events, struct proc *p)
1362{
1363	struct apm_softc *sc = &apm_softc;
1364	int revents = 0;
1365
1366	if (events & (POLLIN | POLLRDNORM)) {
1367		if (sc->event_count) {
1368			revents |= events & (POLLIN | POLLRDNORM);
1369		} else {
1370			selrecord(p, &sc->sc_rsel);
1371		}
1372	}
1373
1374	return (revents);
1375}
1376
1377static device_method_t apm_methods[] = {
1378	/* Device interface */
1379	DEVMETHOD(device_identify,	apm_identify),
1380	DEVMETHOD(device_probe,		apm_probe),
1381	DEVMETHOD(device_attach,	apm_attach),
1382
1383	{ 0, 0 }
1384};
1385
1386static driver_t apm_driver = {
1387	"apm",
1388	apm_methods,
1389	1,			/* no softc (XXX) */
1390};
1391
1392static devclass_t apm_devclass;
1393
1394DEV_DRIVER_MODULE(apm, nexus, apm_driver, apm_devclass, apm_cdevsw, 0, 0);
1395