apm.c revision 50157
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.100 1999/08/21 06:24:11 msmith 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 * probe for APM BIOS
804 */
805static int
806apm_probe(device_t dev)
807{
808#define APM_KERNBASE	KERNBASE
809	struct vm86frame	vmf;
810	struct apm_softc	*sc = &apm_softc;
811	int			disabled, flags;
812
813	if (resource_int_value("apm", 0, "disabled", &disabled) == 0
814	    && disabled != 0)
815		return ENXIO;
816
817	device_set_desc(dev, "APM BIOS");
818
819	if ( device_get_unit(dev) > 0 ) {
820		printf("apm: Only one APM driver supported.\n");
821		return ENXIO;
822	}
823
824	if (resource_int_value("apm", 0, "flags", &flags) != 0)
825		flags = 0;
826
827	bzero(&vmf, sizeof(struct vm86frame));		/* safety */
828	bzero(&apm_softc, sizeof(apm_softc));
829	vmf.vmf_ah = APM_BIOS;
830	vmf.vmf_al = APM_INSTCHECK;
831	vmf.vmf_bx = 0;
832	if (vm86_intcall(APM_INT, &vmf))
833		return ENXIO;			/* APM not found */
834	if (vmf.vmf_bx != 0x504d) {
835		printf("apm: incorrect signature (0x%x)\n", vmf.vmf_bx);
836		return ENXIO;
837	}
838	if ((vmf.vmf_cx & (APM_32BIT_SUPPORT | APM_16BIT_SUPPORT)) == 0) {
839		printf("apm: protected mode connections are not supported\n");
840		return ENXIO;
841	}
842
843	apm_version = vmf.vmf_ax;
844	sc->slow_idle_cpu = ((vmf.vmf_cx & APM_CPUIDLE_SLOW) != 0);
845	sc->disabled = ((vmf.vmf_cx & APM_DISABLED) != 0);
846	sc->disengaged = ((vmf.vmf_cx & APM_DISENGAGED) != 0);
847
848	vmf.vmf_ah = APM_BIOS;
849	vmf.vmf_al = APM_DISCONNECT;
850	vmf.vmf_bx = 0;
851        vm86_intcall(APM_INT, &vmf);		/* disconnect, just in case */
852
853	if ((vmf.vmf_cx & APM_32BIT_SUPPORT) != 0) {
854		vmf.vmf_ah = APM_BIOS;
855		vmf.vmf_al = APM_PROT32CONNECT;
856		vmf.vmf_bx = 0;
857		if (vm86_intcall(APM_INT, &vmf)) {
858			printf("apm: 32-bit connection error.\n");
859			return (ENXIO);
860 		}
861		sc->bios.seg.code32.base = (vmf.vmf_ax << 4) + APM_KERNBASE;
862		sc->bios.seg.code32.limit = 0xffff;
863		sc->bios.seg.code16.base = (vmf.vmf_cx << 4) + APM_KERNBASE;
864		sc->bios.seg.code16.limit = 0xffff;
865		sc->bios.seg.data.base = (vmf.vmf_dx << 4) + APM_KERNBASE;
866		sc->bios.seg.data.limit = 0xffff;
867		sc->bios.entry = vmf.vmf_ebx;
868		sc->connectmode = APM_PROT32CONNECT;
869 	} else {
870		/* use 16-bit connection */
871		vmf.vmf_ah = APM_BIOS;
872		vmf.vmf_al = APM_PROT16CONNECT;
873		vmf.vmf_bx = 0;
874		if (vm86_intcall(APM_INT, &vmf)) {
875			printf("apm: 16-bit connection error.\n");
876			return (ENXIO);
877		}
878		sc->bios.seg.code16.base = (vmf.vmf_ax << 4) + APM_KERNBASE;
879		sc->bios.seg.code16.limit = 0xffff;
880		sc->bios.seg.data.base = (vmf.vmf_cx << 4) + APM_KERNBASE;
881		sc->bios.seg.data.limit = 0xffff;
882		sc->bios.entry = vmf.vmf_bx;
883		sc->connectmode = APM_PROT16CONNECT;
884	}
885	return(0);
886}
887
888
889/*
890 * return 0 if the user will notice and handle the event,
891 * return 1 if the kernel driver should do so.
892 */
893static int
894apm_record_event(struct apm_softc *sc, u_int event_type)
895{
896	struct apm_event_info *evp;
897
898	if ((sc->sc_flags & SCFLAG_OPEN) == 0)
899		return 1;		/* no user waiting */
900	if (sc->event_count == APM_NEVENTS)
901		return 1;			/* overflow */
902	if (sc->event_filter[event_type] == 0)
903		return 1;		/* not registered */
904	evp = &sc->event_list[sc->event_ptr];
905	sc->event_count++;
906	sc->event_ptr++;
907	sc->event_ptr %= APM_NEVENTS;
908	evp->type = event_type;
909	evp->index = ++apm_evindex;
910	selwakeup(&sc->sc_rsel);
911	return (sc->sc_flags & SCFLAG_OCTL) ? 0 : 1; /* user may handle */
912}
913
914/* Process APM event */
915static void
916apm_processevent(void)
917{
918	int apm_event;
919	struct apm_softc *sc = &apm_softc;
920
921#ifdef APM_DEBUG
922#  define OPMEV_DEBUGMESSAGE(symbol) case symbol: \
923	printf("Received APM Event: " #symbol "\n");
924#else
925#  define OPMEV_DEBUGMESSAGE(symbol) case symbol:
926#endif
927	do {
928		apm_event = apm_getevent();
929		switch (apm_event) {
930		    OPMEV_DEBUGMESSAGE(PMEV_STANDBYREQ);
931			if (apm_op_inprog == 0) {
932			    apm_op_inprog++;
933			    if (apm_record_event(sc, apm_event)) {
934				apm_suspend(PMST_STANDBY);
935			    }
936			}
937			break;
938		    OPMEV_DEBUGMESSAGE(PMEV_SUSPENDREQ);
939 			apm_lastreq_notify();
940			if (apm_op_inprog == 0) {
941			    apm_op_inprog++;
942			    if (apm_record_event(sc, apm_event)) {
943				apm_do_suspend();
944			    }
945			}
946			return; /* XXX skip the rest */
947		    OPMEV_DEBUGMESSAGE(PMEV_USERSUSPENDREQ);
948 			apm_lastreq_notify();
949			if (apm_op_inprog == 0) {
950			    apm_op_inprog++;
951			    if (apm_record_event(sc, apm_event)) {
952				apm_do_suspend();
953			    }
954			}
955			return; /* XXX skip the rest */
956		    OPMEV_DEBUGMESSAGE(PMEV_CRITSUSPEND);
957			apm_do_suspend();
958			break;
959		    OPMEV_DEBUGMESSAGE(PMEV_NORMRESUME);
960			apm_record_event(sc, apm_event);
961			apm_resume();
962			break;
963		    OPMEV_DEBUGMESSAGE(PMEV_CRITRESUME);
964			apm_record_event(sc, apm_event);
965			apm_resume();
966			break;
967		    OPMEV_DEBUGMESSAGE(PMEV_STANDBYRESUME);
968			apm_record_event(sc, apm_event);
969			apm_resume();
970			break;
971		    OPMEV_DEBUGMESSAGE(PMEV_BATTERYLOW);
972			if (apm_record_event(sc, apm_event)) {
973			    apm_battery_low();
974			    apm_suspend(PMST_SUSPEND);
975			}
976			break;
977		    OPMEV_DEBUGMESSAGE(PMEV_POWERSTATECHANGE);
978			apm_record_event(sc, apm_event);
979			break;
980		    OPMEV_DEBUGMESSAGE(PMEV_UPDATETIME);
981			apm_record_event(sc, apm_event);
982			inittodr(0);	/* adjust time to RTC */
983			break;
984		    case PMEV_NOEVENT:
985			break;
986		    default:
987			printf("Unknown Original APM Event 0x%x\n", apm_event);
988			    break;
989		}
990	} while (apm_event != PMEV_NOEVENT);
991}
992
993/*
994 * Attach APM:
995 *
996 * Initialize APM driver
997 */
998
999static int
1000apm_attach(device_t dev)
1001{
1002	struct apm_softc	*sc = &apm_softc;
1003	int			flags;
1004	int			drv_version;
1005
1006	if (resource_int_value("apm", 0, "flags", &flags) != 0)
1007		flags = 0;
1008
1009	if (flags & 0x20)
1010		statclock_disable = 1;
1011
1012	sc->initialized = 0;
1013
1014	/* Must be externally enabled */
1015	sc->active = 0;
1016
1017	/* Always call HLT in idle loop */
1018	sc->always_halt_cpu = 1;
1019
1020	/* print bootstrap messages */
1021#ifdef APM_DEBUG
1022	printf("apm: APM BIOS version %04x\n",  apm_version);
1023	printf("apm: Code16 0x%08x, Data 0x%08x\n",
1024               sc->bios.seg.code16.base, sc->bios.seg.data.base);
1025	printf("apm: Code entry 0x%08x, Idling CPU %s, Management %s\n",
1026               sc->bios.entry, is_enabled(sc->slow_idle_cpu),
1027	       is_enabled(!sc->disabled));
1028	printf("apm: CS_limit=0x%x, DS_limit=0x%x\n",
1029	      sc->bios.seg.code16.limit, sc->bios.seg.data.limit);
1030#endif /* APM_DEBUG */
1031
1032#if 0
1033	/*
1034	 * XXX this may not be needed anymore
1035	 */
1036	if ((flags & 0x10)) {
1037		if ((flags & 0xf) >= 0x2) {
1038			apm_driver_version(0x102);
1039		}
1040		if (!apm_version && (flags & 0xf) >= 0x1) {
1041			apm_driver_version(0x101);
1042		}
1043	} else {
1044		apm_driver_version(0x102);
1045		if (!apm_version)
1046			apm_driver_version(0x101);
1047	}
1048#endif
1049	/*
1050        * In one test, apm bios version was 1.02; an attempt to register
1051        * a 1.04 driver resulted in a 1.00 connection!  Registering a
1052        * 1.02 driver resulted in a 1.02 connection.
1053        */
1054	drv_version = apm_version > 0x102 ? 0x102 : apm_version;
1055	for (; drv_version > 0x100; drv_version--)
1056		if (apm_driver_version(drv_version) == 0)
1057			break;
1058	sc->minorversion = ((drv_version & 0x00f0) >>  4) * 10 +
1059		((drv_version & 0x000f) >> 0);
1060	sc->majorversion = ((drv_version & 0xf000) >> 12) * 10 +
1061		((apm_version & 0x0f00) >> 8);
1062
1063	sc->intversion = INTVERSION(sc->majorversion, sc->minorversion);
1064
1065#ifdef APM_DEBUG
1066	if (sc->intversion >= INTVERSION(1, 1))
1067		printf("apm: Engaged control %s\n", is_enabled(!sc->disengaged));
1068#endif
1069
1070	printf("apm: found APM BIOS v%ld.%ld, connected at v%d.%d\n",
1071	       ((apm_version & 0xf000) >> 12) * 10 + ((apm_version & 0x0f00) >> 8),
1072	       ((apm_version & 0x00f0) >> 4) * 10 + ((apm_version & 0x000f) >> 0),
1073	       sc->majorversion, sc->minorversion);
1074
1075#ifdef APM_DEBUG
1076	printf("apm: Slow Idling CPU %s\n", is_enabled(sc->slow_idle_cpu));
1077#endif
1078
1079	/* enable power management */
1080	if (sc->disabled) {
1081		if (apm_enable_disable_pm(1)) {
1082#ifdef APM_DEBUG
1083			printf("apm: *Warning* enable function failed! [%x]\n",
1084				(sc->bios.r.eax >> 8) & 0xff);
1085#endif
1086		}
1087	}
1088
1089	/* engage power managment (APM 1.1 or later) */
1090	if (sc->intversion >= INTVERSION(1, 1) && sc->disengaged) {
1091		if (apm_engage_disengage_pm(1)) {
1092#ifdef APM_DEBUG
1093			printf("apm: *Warning* engage function failed err=[%x]",
1094				(sc->bios.r.eax >> 8) & 0xff);
1095			printf(" (Docked or using external power?).\n");
1096#endif
1097		}
1098	}
1099
1100        /* default suspend hook */
1101        sc->sc_suspend.ah_fun = apm_default_suspend;
1102        sc->sc_suspend.ah_arg = sc;
1103        sc->sc_suspend.ah_name = "default suspend";
1104        sc->sc_suspend.ah_order = APM_MAX_ORDER;
1105
1106        /* default resume hook */
1107        sc->sc_resume.ah_fun = apm_default_resume;
1108        sc->sc_resume.ah_arg = sc;
1109        sc->sc_resume.ah_name = "default resume";
1110        sc->sc_resume.ah_order = APM_MIN_ORDER;
1111
1112        apm_hook_establish(APM_HOOK_SUSPEND, &sc->sc_suspend);
1113        apm_hook_establish(APM_HOOK_RESUME , &sc->sc_resume);
1114
1115	/* Power the system off using APM */
1116	EVENTHANDLER_REGISTER(shutdown_final, apm_power_off, NULL,
1117			      SHUTDOWN_PRI_LAST);
1118
1119	sc->initialized = 1;
1120
1121#ifdef DEVFS
1122	sc->sc_devfs_token =
1123		devfs_add_devswf(&apm_cdevsw, 0, DV_CHR, 0, 0, 0600, "apm");
1124#endif
1125	return 0;
1126}
1127
1128static int
1129apmopen(dev_t dev, int flag, int fmt, struct proc *p)
1130{
1131	struct apm_softc *sc = &apm_softc;
1132	int ctl = APMDEV(dev);
1133
1134	if (!sc->initialized)
1135		return (ENXIO);
1136
1137	switch (ctl) {
1138	case APMDEV_CTL:
1139		if (!(flag & FWRITE))
1140			return EINVAL;
1141		if (sc->sc_flags & SCFLAG_OCTL)
1142			return EBUSY;
1143		sc->sc_flags |= SCFLAG_OCTL;
1144		bzero(sc->event_filter, sizeof sc->event_filter);
1145		break;
1146	case APMDEV_NORMAL:
1147		sc->sc_flags |= SCFLAG_ONORMAL;
1148		break;
1149	default:
1150		return ENXIO;
1151		break;
1152	}
1153	return 0;
1154}
1155
1156static int
1157apmclose(dev_t dev, int flag, int fmt, struct proc *p)
1158{
1159	struct apm_softc *sc = &apm_softc;
1160	int ctl = APMDEV(dev);
1161
1162	switch (ctl) {
1163	case APMDEV_CTL:
1164		apm_lastreq_rejected();
1165		sc->sc_flags &= ~SCFLAG_OCTL;
1166		bzero(sc->event_filter, sizeof sc->event_filter);
1167		break;
1168	case APMDEV_NORMAL:
1169		sc->sc_flags &= ~SCFLAG_ONORMAL;
1170		break;
1171	}
1172	if ((sc->sc_flags & SCFLAG_OPEN) == 0) {
1173		sc->event_count = 0;
1174		sc->event_ptr = 0;
1175	}
1176	return 0;
1177}
1178
1179static int
1180apmioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct proc *p)
1181{
1182	struct apm_softc *sc = &apm_softc;
1183	struct apm_bios_arg *args;
1184	int error = 0;
1185	int ret;
1186	int newstate;
1187
1188	if (!sc->initialized)
1189		return (ENXIO);
1190#ifdef APM_DEBUG
1191	printf("APM ioctl: cmd = 0x%x\n", cmd);
1192#endif
1193	switch (cmd) {
1194	case APMIO_SUSPEND:
1195		if (sc->active)
1196			apm_suspend(PMST_SUSPEND);
1197		else
1198			error = EINVAL;
1199		break;
1200
1201	case APMIO_STANDBY:
1202		if (sc->active)
1203			apm_suspend(PMST_STANDBY);
1204		else
1205			error = EINVAL;
1206		break;
1207
1208	case APMIO_GETINFO_OLD:
1209		{
1210			struct apm_info info;
1211			apm_info_old_t aiop;
1212
1213			if (apm_get_info(&info))
1214				error = ENXIO;
1215			aiop = (apm_info_old_t)addr;
1216			aiop->ai_major = info.ai_major;
1217			aiop->ai_minor = info.ai_minor;
1218			aiop->ai_acline = info.ai_acline;
1219			aiop->ai_batt_stat = info.ai_batt_stat;
1220			aiop->ai_batt_life = info.ai_batt_life;
1221			aiop->ai_status = info.ai_status;
1222		}
1223		break;
1224	case APMIO_GETINFO:
1225		if (apm_get_info((apm_info_t)addr))
1226			error = ENXIO;
1227		break;
1228	case APMIO_ENABLE:
1229		apm_event_enable();
1230		break;
1231	case APMIO_DISABLE:
1232		apm_event_disable();
1233		break;
1234	case APMIO_HALTCPU:
1235		apm_halt_cpu();
1236		break;
1237	case APMIO_NOTHALTCPU:
1238		apm_not_halt_cpu();
1239		break;
1240	case APMIO_DISPLAY:
1241		newstate = *(int *)addr;
1242		if (apm_display(newstate))
1243			error = ENXIO;
1244		break;
1245	case APMIO_BIOS:
1246		/* XXX compatibility with the old interface */
1247		args = (struct apm_bios_arg *)addr;
1248		sc->bios.r.eax = args->eax;
1249		sc->bios.r.ebx = args->ebx;
1250		sc->bios.r.ecx = args->ecx;
1251		sc->bios.r.edx = args->edx;
1252		sc->bios.r.esi = args->esi;
1253		sc->bios.r.edi = args->edi;
1254		if ((ret = apm_bioscall())) {
1255			/*
1256			 * Return code 1 means bios call was unsuccessful.
1257			 * Error code is stored in %ah.
1258			 * Return code -1 means bios call was unsupported
1259			 * in the APM BIOS version.
1260			 */
1261			if (ret == -1) {
1262				error = EINVAL;
1263			}
1264		} else {
1265			/*
1266			 * Return code 0 means bios call was successful.
1267			 * We need only %al and can discard %ah.
1268			 */
1269			sc->bios.r.eax &= 0xff;
1270		}
1271		args->eax = sc->bios.r.eax;
1272		args->ebx = sc->bios.r.ebx;
1273		args->ecx = sc->bios.r.ecx;
1274		args->edx = sc->bios.r.edx;
1275		args->esi = sc->bios.r.esi;
1276		args->edi = sc->bios.r.edi;
1277		break;
1278	default:
1279		error = EINVAL;
1280		break;
1281	}
1282
1283	/* for /dev/apmctl */
1284	if (APMDEV(dev) == APMDEV_CTL) {
1285		struct apm_event_info *evp;
1286		int i;
1287
1288		error = 0;
1289		switch (cmd) {
1290		case APMIO_NEXTEVENT:
1291			if (!sc->event_count) {
1292				error = EAGAIN;
1293			} else {
1294				evp = (struct apm_event_info *)addr;
1295				i = sc->event_ptr + APM_NEVENTS - sc->event_count;
1296				i %= APM_NEVENTS;
1297				*evp = sc->event_list[i];
1298				sc->event_count--;
1299			}
1300			break;
1301		case APMIO_REJECTLASTREQ:
1302			if (apm_lastreq_rejected()) {
1303				error = EINVAL;
1304			}
1305			break;
1306		default:
1307			error = EINVAL;
1308			break;
1309		}
1310	}
1311
1312	return error;
1313}
1314
1315static int
1316apmwrite(dev_t dev, struct uio *uio, int ioflag)
1317{
1318	struct apm_softc *sc = &apm_softc;
1319	u_int event_type;
1320	int error;
1321	u_char enabled;
1322
1323	if (APMDEV(dev) != APMDEV_CTL)
1324		return(ENODEV);
1325	if (uio->uio_resid != sizeof(u_int))
1326		return(E2BIG);
1327
1328	if ((error = uiomove((caddr_t)&event_type, sizeof(u_int), uio)))
1329		return(error);
1330
1331	if (event_type < 0 || event_type >= APM_NPMEV)
1332		return(EINVAL);
1333
1334	if (sc->event_filter[event_type] == 0) {
1335		enabled = 1;
1336	} else {
1337		enabled = 0;
1338	}
1339	sc->event_filter[event_type] = enabled;
1340#ifdef APM_DEBUG
1341	printf("apmwrite: event 0x%x %s\n", event_type, is_enabled(enabled));
1342#endif
1343
1344	return uio->uio_resid;
1345}
1346
1347static int
1348apmpoll(dev_t dev, int events, struct proc *p)
1349{
1350	struct apm_softc *sc = &apm_softc;
1351	int revents = 0;
1352
1353	if (events & (POLLIN | POLLRDNORM)) {
1354		if (sc->event_count) {
1355			revents |= events & (POLLIN | POLLRDNORM);
1356		} else {
1357			selrecord(p, &sc->sc_rsel);
1358		}
1359	}
1360
1361	return (revents);
1362}
1363
1364static device_method_t apm_methods[] = {
1365	/* Device interface */
1366	DEVMETHOD(device_probe,		apm_probe),
1367	DEVMETHOD(device_attach,	apm_attach),
1368
1369	{ 0, 0 }
1370};
1371
1372static driver_t apm_driver = {
1373	"apm",
1374	apm_methods,
1375	1,			/* no softc (XXX) */
1376};
1377
1378static devclass_t apm_devclass;
1379
1380DEV_DRIVER_MODULE(apm, nexus, apm_driver, apm_devclass, apm_cdevsw, 0, 0);
1381