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