apm.c revision 74810
165556Sjasone/*
265556Sjasone * APM (Advanced Power Management) BIOS Device Driver
365556Sjasone *
465556Sjasone * Copyright (c) 1994 UKAI, Fumitoshi.
565556Sjasone * Copyright (c) 1994-1995 by HOSOKAWA, Tatsumi <hosokawa@jp.FreeBSD.org>
665556Sjasone * Copyright (c) 1996 Nate Williams <nate@FreeBSD.org>
765556Sjasone * Copyright (c) 1997 Poul-Henning Kamp <phk@FreeBSD.org>
865556Sjasone *
965556Sjasone * This software may be used, modified, copied, and distributed, in
1065556Sjasone * both source and binary form provided that the above copyright and
1165556Sjasone * these terms are retained. Under no circumstances is the author
1265556Sjasone * responsible for the proper functioning of this software, nor does
1365556Sjasone * the author assume any responsibility for damages incurred with its
1465556Sjasone * use.
1565556Sjasone *
1665556Sjasone * Sep, 1994	Implemented on FreeBSD 1.1.5.1R (Toshiba AVS001WD)
1769012Sjhb *
1865556Sjasone * $FreeBSD: head/sys/i386/bios/apm.c 74810 2001-03-26 12:41:29Z phk $
1965556Sjasone */
2069012Sjhb
2165556Sjasone#include <sys/param.h>
2265556Sjasone#include <sys/systm.h>
2365556Sjasone#include <sys/eventhandler.h>
2465556Sjasone#include <sys/conf.h>
2565556Sjasone#include <sys/kernel.h>
2665556Sjasone#include <sys/time.h>
2765556Sjasone#include <sys/reboot.h>
2865556Sjasone#include <sys/bus.h>
2965556Sjasone#include <sys/selinfo.h>
3065556Sjasone#include <sys/poll.h>
3168420Sjhb#include <sys/fcntl.h>
3268420Sjhb#include <sys/uio.h>
3365556Sjasone#include <sys/signalvar.h>
3465556Sjasone#include <sys/sysctl.h>
35116182Sobrien#include <machine/apm_bios.h>
36116182Sobrien#include <machine/segments.h>
37116182Sobrien#include <machine/clock.h>
3870035Sjhb#include <vm/vm.h>
3968420Sjhb#include <vm/vm_param.h>
40103787Sjeff#include <vm/pmap.h>
4168420Sjhb#include <sys/syslog.h>
4270035Sjhb
43103787Sjeff#include <machine/pc/bios.h>
4470035Sjhb#include <machine/vm86.h>
4570705Sjhb
4665556Sjasone#include <i386/apm/apm.h>
4768420Sjhb
4887793Sjhb/* Used by the apm_saver screen saver module */
4965556Sjasoneint apm_display __P((int newstate));
5068420Sjhbstruct apm_softc apm_softc;
5178784Sjhb
5265556Sjasonestatic void apm_resume __P((void));
5393503Sjakestatic int apm_bioscall(void);
5493950Sjakestatic int apm_check_function_supported __P((u_int version, u_int func));
5593950Sjake
5693950Sjakestatic u_long	apm_version;
5793503Sjake
58103787Sjeffint	apm_evindex;
5970035Sjhb
6070035Sjhb#define	SCFLAG_ONORMAL	0x0000001
6178784Sjhb#define	SCFLAG_OCTL	0x0000002
6278784Sjhb#define	SCFLAG_OPEN	(SCFLAG_ONORMAL|SCFLAG_OCTL)
6378784Sjhb
6478784Sjhb#define APMDEV(dev)	(minor(dev)&0x0f)
6568420Sjhb#define APMDEV_NORMAL	0
6668420Sjhb#define APMDEV_CTL	8
6768420Sjhb
6868420Sjhbstatic struct apmhook	*hook[NAPM_HOOK];		/* XXX */
6968420Sjhb
7068420Sjhb#define is_enabled(foo) ((foo) ? "enabled" : "disabled")
7168420Sjhb
7268420Sjhb/* Map version number to integer (keeps ordering of version numbers) */
7393503Sjake#define INTVERSION(major, minor)	((major)*100 + (minor))
7493503Sjake
7568420Sjhbstatic struct callout_handle apm_timeout_ch =
7668420Sjhb    CALLOUT_HANDLE_INITIALIZER(&apm_timeout_ch);
7793503Sjake
7893503Sjakestatic timeout_t apm_timeout;
7970705Sjhbstatic d_open_t apmopen;
8070705Sjhbstatic d_close_t apmclose;
8170705Sjhbstatic d_write_t apmwrite;
8270705Sjhbstatic d_ioctl_t apmioctl;
8377843Speterstatic d_poll_t apmpoll;
8477900Speter
8570705Sjhb#define CDEV_MAJOR 39
8665556Sjasonestatic struct cdevsw apm_cdevsw = {
8777843Speter	/* open */	apmopen,
8877900Speter	/* close */	apmclose,
8970705Sjhb	/* read */	noread,
9065556Sjasone	/* write */	apmwrite,
9165556Sjasone	/* ioctl */	apmioctl,
9270705Sjhb	/* poll */	apmpoll,
9365556Sjasone	/* mmap */	nommap,
9493503Sjake	/* strategy */	nostrategy,
9593503Sjake	/* name */	"apm",
9693503Sjake	/* maj */	CDEV_MAJOR,
9765556Sjasone	/* dump */	nodump,
9865556Sjasone	/* psize */	nopsize,
9968420Sjhb	/* flags */	0,
10093503Sjake};
10193503Sjake
10277900Speterstatic int apm_suspend_delay = 1;
10370705Sjhbstatic int apm_standby_delay = 1;
10493503Sjakestatic int apm_debug = 0;
10568420Sjhb
106103787Sjeff#define APM_DPRINT(args...) do	{					\
107103787Sjeff	if (apm_debug) {						\
108103787Sjeff		printf(args);						\
109103787Sjeff	}								\
110103787Sjeff} while (0)
111103787Sjeff
112103787SjeffSYSCTL_INT(_machdep, OID_AUTO, apm_suspend_delay, CTLFLAG_RW, &apm_suspend_delay, 1, "");
113103787SjeffSYSCTL_INT(_machdep, OID_AUTO, apm_standby_delay, CTLFLAG_RW, &apm_standby_delay, 1, "");
114103787SjeffSYSCTL_INT(_debug, OID_AUTO, apm_debug, CTLFLAG_RW, &apm_debug, 0, "");
115103787Sjeff
116103787Sjeff/*
117103787Sjeff * return  0 if the function successfull,
118103787Sjeff * return  1 if the function unsuccessfull,
119103787Sjeff * return -1 if the function unsupported.
120103787Sjeff */
121103787Sjeffstatic int
122103787Sjeffapm_bioscall(void)
123103787Sjeff{
124103787Sjeff	struct apm_softc *sc = &apm_softc;
125103787Sjeff	int errno = 0;
126103787Sjeff	u_int apm_func = sc->bios.r.eax & 0xff;
127103787Sjeff
128103787Sjeff	if (!apm_check_function_supported(sc->intversion, apm_func)) {
129103787Sjeff		APM_DPRINT("apm_bioscall: function 0x%x is not supported in v%d.%d\n",
130103787Sjeff		    apm_func, sc->majorversion, sc->minorversion);
131103787Sjeff		return (-1);
132103787Sjeff	}
133103787Sjeff
134103787Sjeff	sc->bios_busy = 1;
135103787Sjeff	if (sc->connectmode == APM_PROT32CONNECT) {
136103787Sjeff		set_bios_selectors(&sc->bios.seg,
137103787Sjeff				   BIOSCODE_FLAG | BIOSDATA_FLAG);
138103787Sjeff		errno = bios32(&sc->bios.r,
139103787Sjeff			       sc->bios.entry, GSEL(GBIOSCODE32_SEL, SEL_KPL));
140103787Sjeff	} else {
141103787Sjeff		errno = bios16(&sc->bios, NULL);
142103787Sjeff	}
143103787Sjeff	sc->bios_busy = 0;
144116697Srwatson	return (errno);
145116697Srwatson}
146116697Srwatson
147103787Sjeff/* check whether APM function is supported (1)  or not (0). */
148103787Sjeffstatic int
149103787Sjeffapm_check_function_supported(u_int version, u_int func)
150103787Sjeff{
151103787Sjeff	/* except driver version */
152103787Sjeff	if (func == APM_DRVVERSION) {
153103787Sjeff		return (1);
154103787Sjeff	}
155103787Sjeff
156103787Sjeff	switch (version) {
157103787Sjeff	case INTVERSION(1, 0):
158103787Sjeff		if (func > APM_GETPMEVENT) {
159103787Sjeff			return (0); /* not supported */
160103787Sjeff		}
161103787Sjeff		break;
162103787Sjeff	case INTVERSION(1, 1):
163103787Sjeff		if (func > APM_ENGAGEDISENGAGEPM &&
164103787Sjeff		    func < APM_OEMFUNC) {
165103787Sjeff			return (0); /* not supported */
166103787Sjeff		}
167103787Sjeff		break;
16868420Sjhb	case INTVERSION(1, 2):
16993503Sjake		break;
17093503Sjake	}
17193503Sjake
17268420Sjhb	return (1); /* supported */
17368420Sjhb}
174103787Sjeff
175103787Sjeff/* enable/disable power management */
176103787Sjeffstatic int
17774903Sjhbapm_enable_disable_pm(int enable)
178103787Sjeff{
179103787Sjeff	struct apm_softc *sc = &apm_softc;
18087793Sjhb
18193503Sjake	sc->bios.r.eax = (APM_BIOS << 8) | APM_ENABLEDISABLEPM;
18291904Sjhb
18368420Sjhb	if (sc->intversion >= INTVERSION(1, 1))
18469880Sjhb		sc->bios.r.ebx  = PMDV_ALLDEV;
18569880Sjhb	else
18668420Sjhb		sc->bios.r.ebx  = 0xffff;	/* APM version 1.0 only */
18768420Sjhb	sc->bios.r.ecx  = enable;
18893503Sjake	sc->bios.r.edx = 0;
18993503Sjake	return (apm_bioscall());
19093503Sjake}
191103787Sjeff
19287793Sjhb/* register driver version (APM 1.1 or later) */
193116101Sjhbstatic int
19487793Sjhbapm_driver_version(int version)
195116101Sjhb{
19693503Sjake	struct apm_softc *sc = &apm_softc;
197103787Sjeff
198103787Sjeff	sc->bios.r.eax = (APM_BIOS << 8) | APM_DRVVERSION;
199103787Sjeff	sc->bios.r.ebx  = 0x0;
200114471Sjulian	sc->bios.r.ecx  = version;
201103995Sjeff	sc->bios.r.edx = 0;
202103787Sjeff
203103787Sjeff	if (apm_bioscall() == 0 && sc->bios.r.eax == version)
204103787Sjeff		return (0);
205103787Sjeff
206103787Sjeff	/* Some old BIOSes don't return the connection version in %ax. */
207103787Sjeff	if (sc->bios.r.eax == ((APM_BIOS << 8) | APM_DRVVERSION))
208103787Sjeff		return (0);
209103787Sjeff
210103787Sjeff	return (1);
211103787Sjeff}
212103787Sjeff
21368420Sjhb/* engage/disengage power management (APM 1.1 or later) */
21468420Sjhbstatic int
21568420Sjhbapm_engage_disengage_pm(int engage)
21668420Sjhb{
21768420Sjhb	struct apm_softc *sc = &apm_softc;
218103787Sjeff
21993503Sjake	sc->bios.r.eax = (APM_BIOS << 8) | APM_ENGAGEDISENGAGEPM;
22091904Sjhb	sc->bios.r.ebx = PMDV_ALLDEV;
221112105Sjhb	sc->bios.r.ecx = engage;
222112105Sjhb	sc->bios.r.edx = 0;
223112105Sjhb	return (apm_bioscall());
22493503Sjake}
22568420Sjhb
22693503Sjake/* get PM event */
22768420Sjhbstatic u_int
22868782Sjhbapm_getevent(void)
22993503Sjake{
23068782Sjhb	struct apm_softc *sc = &apm_softc;
23193503Sjake
23293503Sjake	sc->bios.r.eax = (APM_BIOS << 8) | APM_GETPMEVENT;
23393503Sjake
23493503Sjake	sc->bios.r.ebx = 0;
23593503Sjake	sc->bios.r.ecx = 0;
23668420Sjhb	sc->bios.r.edx = 0;
23768420Sjhb	if (apm_bioscall())
23893503Sjake		return (PMEV_NOEVENT);
23968420Sjhb	return (sc->bios.r.ebx & 0xffff);
24093503Sjake}
24193503Sjake
24293503Sjake/* suspend entire system */
24393503Sjakestatic int
24493503Sjakeapm_suspend_system(int state)
24593503Sjake{
246103787Sjeff	struct apm_softc *sc = &apm_softc;
247103787Sjeff
248103787Sjeff	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
249103787Sjeff	sc->bios.r.ebx = PMDV_ALLDEV;
250103787Sjeff	sc->bios.r.ecx = state;
251103787Sjeff	sc->bios.r.edx = 0;
252116101Sjhb
25368420Sjhb	if (apm_bioscall()) {
25468420Sjhb 		printf("Entire system suspend failure: errcode = %d\n",
25570035Sjhb		       0xff & (sc->bios.r.eax >> 8));
25670035Sjhb 		return 1;
25770035Sjhb 	}
25870035Sjhb 	return 0;
25970035Sjhb}
26070035Sjhb
26170035Sjhb/* Display control */
26270035Sjhb/*
26370035Sjhb * Experimental implementation: My laptop machine can't handle this function
26470035Sjhb * If your laptop can control the display via APM, please inform me.
26570035Sjhb *                            HOSOKAWA, Tatsumi <hosokawa@jp.FreeBSD.org>
26672755Sjhb */
26772755Sjhbint
26872755Sjhbapm_display(int newstate)
26970035Sjhb{
270118269Sjhb	struct apm_softc *sc = &apm_softc;
271118269Sjhb
272118269Sjhb	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
27372755Sjhb	sc->bios.r.ebx = PMDV_DISP0;
27470035Sjhb	sc->bios.r.ecx = newstate ? PMST_APMENABLED:PMST_SUSPEND;
27570035Sjhb	sc->bios.r.edx = 0;
27670035Sjhb	if (apm_bioscall() == 0) {
27770035Sjhb		return 0;
27870035Sjhb 	}
27970035Sjhb
280118269Sjhb	/* If failed, then try to blank all display devices instead. */
281118269Sjhb	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
282118269Sjhb	sc->bios.r.ebx = PMDV_DISPALL;	/* all display devices */
28372755Sjhb	sc->bios.r.ecx = newstate ? PMST_APMENABLED:PMST_SUSPEND;
284118269Sjhb	sc->bios.r.edx = 0;
285118269Sjhb	if (apm_bioscall() == 0) {
286118269Sjhb		return 0;
287118269Sjhb 	}
28872755Sjhb 	printf("Display off failure: errcode = %d\n",
289118269Sjhb	       0xff & (sc->bios.r.eax >> 8));
29070035Sjhb 	return 1;
29170035Sjhb}
29270035Sjhb
29370035Sjhb/*
29470035Sjhb * Turn off the entire system.
29570035Sjhb */
29670035Sjhbstatic void
29770035Sjhbapm_power_off(void *junk, int howto)
29870035Sjhb{
29970035Sjhb	struct apm_softc *sc = &apm_softc;
30070035Sjhb
30170035Sjhb	/* Not halting powering off, or not active */
30270035Sjhb	if (!(howto & RB_POWEROFF) || !apm_softc.active)
30370035Sjhb		return;
30472755Sjhb	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
30572755Sjhb	sc->bios.r.ebx = PMDV_ALLDEV;
30672755Sjhb	sc->bios.r.ecx = PMST_OFF;
30772755Sjhb	sc->bios.r.edx = 0;
30872755Sjhb	(void) apm_bioscall();
30970035Sjhb}
31072755Sjhb
31170035Sjhb/* APM Battery low handler */
31293503Sjakestatic void
31393503Sjakeapm_battery_low(void)
31493503Sjake{
31593503Sjake	printf("\007\007 * * * BATTERY IS LOW * * * \007\007");
31693503Sjake}
31793503Sjake
31893503Sjake/* APM hook manager */
31972755Sjhbstatic struct apmhook *
32070035Sjhbapm_add_hook(struct apmhook **list, struct apmhook *ah)
32170035Sjhb{
32270035Sjhb	int s;
32370035Sjhb	struct apmhook *p, *prev;
32470035Sjhb
32570035Sjhb	APM_DPRINT("Add hook \"%s\"\n", ah->ah_name);
32670035Sjhb
32770035Sjhb	s = splhigh();
32870035Sjhb	if (ah == NULL)
32970035Sjhb		panic("illegal apm_hook!");
33070035Sjhb	prev = NULL;
331	for (p = *list; p != NULL; prev = p, p = p->ah_next)
332		if (p->ah_order > ah->ah_order)
333			break;
334
335	if (prev == NULL) {
336		ah->ah_next = *list;
337		*list = ah;
338	} else {
339		ah->ah_next = prev->ah_next;
340		prev->ah_next = ah;
341	}
342	splx(s);
343	return ah;
344}
345
346static void
347apm_del_hook(struct apmhook **list, struct apmhook *ah)
348{
349	int s;
350	struct apmhook *p, *prev;
351
352	s = splhigh();
353	prev = NULL;
354	for (p = *list; p != NULL; prev = p, p = p->ah_next)
355		if (p == ah)
356			goto deleteit;
357	panic("Tried to delete unregistered apm_hook.");
358	goto nosuchnode;
359deleteit:
360	if (prev != NULL)
361		prev->ah_next = p->ah_next;
362	else
363		*list = p->ah_next;
364nosuchnode:
365	splx(s);
366}
367
368
369/* APM driver calls some functions automatically */
370static void
371apm_execute_hook(struct apmhook *list)
372{
373	struct apmhook *p;
374
375	for (p = list; p != NULL; p = p->ah_next) {
376		APM_DPRINT("Execute APM hook \"%s.\"\n", p->ah_name);
377		if ((*(p->ah_fun))(p->ah_arg))
378			printf("Warning: APM hook \"%s\" failed", p->ah_name);
379	}
380}
381
382
383/* establish an apm hook */
384struct apmhook *
385apm_hook_establish(int apmh, struct apmhook *ah)
386{
387	if (apmh < 0 || apmh >= NAPM_HOOK)
388		return NULL;
389
390	return apm_add_hook(&hook[apmh], ah);
391}
392
393/* disestablish an apm hook */
394void
395apm_hook_disestablish(int apmh, struct apmhook *ah)
396{
397	if (apmh < 0 || apmh >= NAPM_HOOK)
398		return;
399
400	apm_del_hook(&hook[apmh], ah);
401}
402
403static int apm_record_event __P((struct apm_softc *, u_int));
404static void apm_processevent(void);
405
406static u_int apm_op_inprog = 0;
407
408static void
409apm_do_suspend(void)
410{
411	struct apm_softc *sc = &apm_softc;
412	int error;
413
414	if (!sc)
415		return;
416
417	apm_op_inprog = 0;
418	sc->suspends = sc->suspend_countdown = 0;
419
420	if (sc->initialized) {
421		error = DEVICE_SUSPEND(root_bus);
422		if (error) {
423			DEVICE_RESUME(root_bus);
424		} else {
425			apm_execute_hook(hook[APM_HOOK_SUSPEND]);
426			if (apm_suspend_system(PMST_SUSPEND) == 0) {
427				sc->suspending = 1;
428				apm_processevent();
429			} else {
430				/* Failure, 'resume' the system again */
431				apm_execute_hook(hook[APM_HOOK_RESUME]);
432				DEVICE_RESUME(root_bus);
433			}
434		}
435	}
436}
437
438static void
439apm_do_standby(void)
440{
441	struct apm_softc *sc = &apm_softc;
442
443	if (!sc)
444		return;
445
446	apm_op_inprog = 0;
447	sc->standbys = sc->standby_countdown = 0;
448
449	if (sc->initialized) {
450		/*
451		 * As far as standby, we don't need to execute
452		 * all of suspend hooks.
453		 */
454		if (apm_suspend_system(PMST_STANDBY) == 0)
455			apm_processevent();
456	}
457}
458
459static void
460apm_lastreq_notify(void)
461{
462	struct apm_softc *sc = &apm_softc;
463
464	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
465	sc->bios.r.ebx = PMDV_ALLDEV;
466	sc->bios.r.ecx = PMST_LASTREQNOTIFY;
467	sc->bios.r.edx = 0;
468	apm_bioscall();
469}
470
471static int
472apm_lastreq_rejected(void)
473{
474	struct apm_softc *sc = &apm_softc;
475
476	if (apm_op_inprog == 0) {
477		return 1;	/* no operation in progress */
478	}
479
480	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
481	sc->bios.r.ebx = PMDV_ALLDEV;
482	sc->bios.r.ecx = PMST_LASTREQREJECT;
483	sc->bios.r.edx = 0;
484
485	if (apm_bioscall()) {
486		APM_DPRINT("apm_lastreq_rejected: failed\n");
487		return 1;
488	}
489	apm_op_inprog = 0;
490	return 0;
491}
492
493/*
494 * Public interface to the suspend/resume:
495 *
496 * Execute suspend and resume hook before and after sleep, respectively.
497 *
498 */
499
500void
501apm_suspend(int state)
502{
503	struct apm_softc *sc = &apm_softc;
504
505	if (!sc->initialized)
506		return;
507
508	switch (state) {
509	case PMST_SUSPEND:
510		if (sc->suspends)
511			return;
512		sc->suspends++;
513		sc->suspend_countdown = apm_suspend_delay;
514		break;
515	case PMST_STANDBY:
516		if (sc->standbys)
517			return;
518		sc->standbys++;
519		sc->standby_countdown = apm_standby_delay;
520		break;
521	default:
522		printf("apm_suspend: Unknown Suspend state 0x%x\n", state);
523		return;
524	}
525
526	apm_op_inprog++;
527	apm_lastreq_notify();
528}
529
530void
531apm_resume(void)
532{
533	struct apm_softc *sc = &apm_softc;
534
535	if (!sc)
536		return;
537
538	if (sc->suspending == 0)
539		return;
540
541	sc->suspending = 0;
542	if (sc->initialized) {
543		apm_execute_hook(hook[APM_HOOK_RESUME]);
544		DEVICE_RESUME(root_bus);
545	}
546}
547
548
549/* get power status per battery */
550static int
551apm_get_pwstatus(apm_pwstatus_t app)
552{
553	struct apm_softc *sc = &apm_softc;
554
555	if (app->ap_device != PMDV_ALLDEV &&
556	    (app->ap_device < PMDV_BATT0 || app->ap_device > PMDV_BATT_ALL))
557		return 1;
558
559	sc->bios.r.eax = (APM_BIOS << 8) | APM_GETPWSTATUS;
560	sc->bios.r.ebx = app->ap_device;
561	sc->bios.r.ecx = 0;
562	sc->bios.r.edx = 0xffff;	/* default to unknown battery time */
563
564	if (apm_bioscall())
565		return 1;
566
567	app->ap_acline    = (sc->bios.r.ebx >> 8) & 0xff;
568	app->ap_batt_stat = sc->bios.r.ebx & 0xff;
569	app->ap_batt_flag = (sc->bios.r.ecx >> 8) & 0xff;
570	app->ap_batt_life = sc->bios.r.ecx & 0xff;
571	sc->bios.r.edx &= 0xffff;
572	if (sc->bios.r.edx == 0xffff)	/* Time is unknown */
573		app->ap_batt_time = -1;
574	else if (sc->bios.r.edx & 0x8000)	/* Time is in minutes */
575		app->ap_batt_time = (sc->bios.r.edx & 0x7fff) * 60;
576	else				/* Time is in seconds */
577		app->ap_batt_time = sc->bios.r.edx;
578
579	return 0;
580}
581
582
583/* get APM information */
584static int
585apm_get_info(apm_info_t aip)
586{
587	struct apm_softc *sc = &apm_softc;
588	struct apm_pwstatus aps;
589
590	bzero(&aps, sizeof(aps));
591	aps.ap_device = PMDV_ALLDEV;
592	if (apm_get_pwstatus(&aps))
593		return 1;
594
595	aip->ai_infoversion = 1;
596	aip->ai_acline      = aps.ap_acline;
597	aip->ai_batt_stat   = aps.ap_batt_stat;
598	aip->ai_batt_life   = aps.ap_batt_life;
599	aip->ai_batt_time   = aps.ap_batt_time;
600	aip->ai_major       = (u_int)sc->majorversion;
601	aip->ai_minor       = (u_int)sc->minorversion;
602	aip->ai_status      = (u_int)sc->active;
603
604	sc->bios.r.eax = (APM_BIOS << 8) | APM_GETCAPABILITIES;
605	sc->bios.r.ebx = 0;
606	sc->bios.r.ecx = 0;
607	sc->bios.r.edx = 0;
608	if (apm_bioscall()) {
609		aip->ai_batteries = -1;	/* Unknown */
610		aip->ai_capabilities = 0xff00; /* Unknown, with no bits set */
611	} else {
612		aip->ai_batteries = sc->bios.r.ebx & 0xff;
613		aip->ai_capabilities = sc->bios.r.ecx & 0xf;
614	}
615
616	bzero(aip->ai_spare, sizeof aip->ai_spare);
617
618	return 0;
619}
620
621
622/* inform APM BIOS that CPU is idle */
623void
624apm_cpu_idle(void)
625{
626	struct apm_softc *sc = &apm_softc;
627
628	if (sc->active) {
629
630		sc->bios.r.eax = (APM_BIOS <<8) | APM_CPUIDLE;
631		sc->bios.r.edx = sc->bios.r.ecx = sc->bios.r.ebx = 0;
632		(void) apm_bioscall();
633	}
634	/*
635	 * Some APM implementation halts CPU in BIOS, whenever
636	 * "CPU-idle" function are invoked, but swtch() of
637	 * FreeBSD halts CPU, therefore, CPU is halted twice
638	 * in the sched loop. It makes the interrupt latency
639	 * terribly long and be able to cause a serious problem
640	 * in interrupt processing. We prevent it by removing
641	 * "hlt" operation from swtch() and managed it under
642	 * APM driver.
643	 */
644	if (!sc->active || sc->always_halt_cpu)
645		__asm("hlt");	/* wait for interrupt */
646}
647
648/* inform APM BIOS that CPU is busy */
649void
650apm_cpu_busy(void)
651{
652	struct apm_softc *sc = &apm_softc;
653
654	/*
655	 * The APM specification says this is only necessary if your BIOS
656	 * slows down the processor in the idle task, otherwise it's not
657	 * necessary.
658	 */
659	if (sc->slow_idle_cpu && sc->active) {
660
661		sc->bios.r.eax = (APM_BIOS <<8) | APM_CPUBUSY;
662		sc->bios.r.edx = sc->bios.r.ecx = sc->bios.r.ebx = 0;
663		apm_bioscall();
664	}
665}
666
667
668/*
669 * APM timeout routine:
670 *
671 * This routine is automatically called by timer once per second.
672 */
673
674static void
675apm_timeout(void *dummy)
676{
677	struct apm_softc *sc = &apm_softc;
678
679	if (apm_op_inprog)
680		apm_lastreq_notify();
681
682	if (sc->standbys && sc->standby_countdown-- <= 0)
683		apm_do_standby();
684
685	if (sc->suspends && sc->suspend_countdown-- <= 0)
686		apm_do_suspend();
687
688	if (!sc->bios_busy)
689		apm_processevent();
690
691	if (sc->active == 1)
692		/* Run slightly more oftan than 1 Hz */
693		apm_timeout_ch = timeout(apm_timeout, NULL, hz - 1);
694}
695
696/* enable APM BIOS */
697static void
698apm_event_enable(void)
699{
700	struct apm_softc *sc = &apm_softc;
701
702	APM_DPRINT("called apm_event_enable()\n");
703	if (sc->initialized) {
704		sc->active = 1;
705		apm_timeout(sc);
706	}
707}
708
709/* disable APM BIOS */
710static void
711apm_event_disable(void)
712{
713	struct apm_softc *sc = &apm_softc;
714
715	APM_DPRINT("called apm_event_disable()\n");
716	if (sc->initialized) {
717		untimeout(apm_timeout, NULL, apm_timeout_ch);
718		sc->active = 0;
719	}
720}
721
722/* halt CPU in scheduling loop */
723static void
724apm_halt_cpu(void)
725{
726	struct apm_softc *sc = &apm_softc;
727
728	if (sc->initialized)
729		sc->always_halt_cpu = 1;
730}
731
732/* don't halt CPU in scheduling loop */
733static void
734apm_not_halt_cpu(void)
735{
736	struct apm_softc *sc = &apm_softc;
737
738	if (sc->initialized)
739		sc->always_halt_cpu = 0;
740}
741
742/* device driver definitions */
743
744/*
745 * Create "connection point"
746 */
747static void
748apm_identify(driver_t *driver, device_t parent)
749{
750	device_t child;
751
752	child = BUS_ADD_CHILD(parent, 0, "apm", 0);
753	if (child == NULL)
754		panic("apm_identify");
755}
756
757/*
758 * probe for APM BIOS
759 */
760static int
761apm_probe(device_t dev)
762{
763#define APM_KERNBASE	KERNBASE
764	struct vm86frame	vmf;
765	struct apm_softc	*sc = &apm_softc;
766	int			disabled, flags;
767
768	device_set_desc(dev, "APM BIOS");
769
770	if (resource_int_value("apm", 0, "disabled", &disabled) != 0)
771		disabled = 0;
772	if (disabled)
773		return ENXIO;
774
775	if (device_get_unit(dev) > 0) {
776		printf("apm: Only one APM driver supported.\n");
777		return ENXIO;
778	}
779
780	if (resource_int_value("apm", 0, "flags", &flags) != 0)
781		flags = 0;
782
783	bzero(&vmf, sizeof(struct vm86frame));		/* safety */
784	bzero(&apm_softc, sizeof(apm_softc));
785	vmf.vmf_ah = APM_BIOS;
786	vmf.vmf_al = APM_INSTCHECK;
787	vmf.vmf_bx = 0;
788	if (vm86_intcall(APM_INT, &vmf))
789		return ENXIO;			/* APM not found */
790	if (vmf.vmf_bx != 0x504d) {
791		printf("apm: incorrect signature (0x%x)\n", vmf.vmf_bx);
792		return ENXIO;
793	}
794	if ((vmf.vmf_cx & (APM_32BIT_SUPPORT | APM_16BIT_SUPPORT)) == 0) {
795		printf("apm: protected mode connections are not supported\n");
796		return ENXIO;
797	}
798
799	apm_version = vmf.vmf_ax;
800	sc->slow_idle_cpu = ((vmf.vmf_cx & APM_CPUIDLE_SLOW) != 0);
801	sc->disabled = ((vmf.vmf_cx & APM_DISABLED) != 0);
802	sc->disengaged = ((vmf.vmf_cx & APM_DISENGAGED) != 0);
803
804	vmf.vmf_ah = APM_BIOS;
805	vmf.vmf_al = APM_DISCONNECT;
806	vmf.vmf_bx = 0;
807        vm86_intcall(APM_INT, &vmf);		/* disconnect, just in case */
808
809	if ((vmf.vmf_cx & APM_32BIT_SUPPORT) != 0) {
810		vmf.vmf_ah = APM_BIOS;
811		vmf.vmf_al = APM_PROT32CONNECT;
812		vmf.vmf_bx = 0;
813		if (vm86_intcall(APM_INT, &vmf)) {
814			printf("apm: 32-bit connection error.\n");
815			return (ENXIO);
816 		}
817		sc->bios.seg.code32.base = (vmf.vmf_ax << 4) + APM_KERNBASE;
818		sc->bios.seg.code32.limit = 0xffff;
819		sc->bios.seg.code16.base = (vmf.vmf_cx << 4) + APM_KERNBASE;
820		sc->bios.seg.code16.limit = 0xffff;
821		sc->bios.seg.data.base = (vmf.vmf_dx << 4) + APM_KERNBASE;
822		sc->bios.seg.data.limit = 0xffff;
823		sc->bios.entry = vmf.vmf_ebx;
824		sc->connectmode = APM_PROT32CONNECT;
825 	} else {
826		/* use 16-bit connection */
827		vmf.vmf_ah = APM_BIOS;
828		vmf.vmf_al = APM_PROT16CONNECT;
829		vmf.vmf_bx = 0;
830		if (vm86_intcall(APM_INT, &vmf)) {
831			printf("apm: 16-bit connection error.\n");
832			return (ENXIO);
833		}
834		sc->bios.seg.code16.base = (vmf.vmf_ax << 4) + APM_KERNBASE;
835		sc->bios.seg.code16.limit = 0xffff;
836		sc->bios.seg.data.base = (vmf.vmf_cx << 4) + APM_KERNBASE;
837		sc->bios.seg.data.limit = 0xffff;
838		sc->bios.entry = vmf.vmf_bx;
839		sc->connectmode = APM_PROT16CONNECT;
840	}
841	return(0);
842}
843
844
845/*
846 * return 0 if the user will notice and handle the event,
847 * return 1 if the kernel driver should do so.
848 */
849static int
850apm_record_event(struct apm_softc *sc, u_int event_type)
851{
852	struct apm_event_info *evp;
853
854	if ((sc->sc_flags & SCFLAG_OPEN) == 0)
855		return 1;		/* no user waiting */
856	if (sc->event_count == APM_NEVENTS)
857		return 1;			/* overflow */
858	if (sc->event_filter[event_type] == 0)
859		return 1;		/* not registered */
860	evp = &sc->event_list[sc->event_ptr];
861	sc->event_count++;
862	sc->event_ptr++;
863	sc->event_ptr %= APM_NEVENTS;
864	evp->type = event_type;
865	evp->index = ++apm_evindex;
866	selwakeup(&sc->sc_rsel);
867	return (sc->sc_flags & SCFLAG_OCTL) ? 0 : 1; /* user may handle */
868}
869
870/* Process APM event */
871static void
872apm_processevent(void)
873{
874	int apm_event;
875	struct apm_softc *sc = &apm_softc;
876
877#define OPMEV_DEBUGMESSAGE(symbol) case symbol:				\
878	APM_DPRINT("Received APM Event: " #symbol "\n");
879
880	do {
881		apm_event = apm_getevent();
882		switch (apm_event) {
883		    OPMEV_DEBUGMESSAGE(PMEV_STANDBYREQ);
884			if (apm_op_inprog == 0) {
885			    apm_op_inprog++;
886			    if (apm_record_event(sc, apm_event)) {
887				apm_suspend(PMST_STANDBY);
888			    }
889			}
890			break;
891		    OPMEV_DEBUGMESSAGE(PMEV_USERSTANDBYREQ);
892			if (apm_op_inprog == 0) {
893			    apm_op_inprog++;
894			    if (apm_record_event(sc, apm_event)) {
895				apm_suspend(PMST_STANDBY);
896			    }
897			}
898			break;
899		    OPMEV_DEBUGMESSAGE(PMEV_SUSPENDREQ);
900 			apm_lastreq_notify();
901			if (apm_op_inprog == 0) {
902			    apm_op_inprog++;
903			    if (apm_record_event(sc, apm_event)) {
904				apm_do_suspend();
905			    }
906			}
907			return; /* XXX skip the rest */
908		    OPMEV_DEBUGMESSAGE(PMEV_USERSUSPENDREQ);
909 			apm_lastreq_notify();
910			if (apm_op_inprog == 0) {
911			    apm_op_inprog++;
912			    if (apm_record_event(sc, apm_event)) {
913				apm_do_suspend();
914			    }
915			}
916			return; /* XXX skip the rest */
917		    OPMEV_DEBUGMESSAGE(PMEV_CRITSUSPEND);
918			apm_do_suspend();
919			break;
920		    OPMEV_DEBUGMESSAGE(PMEV_NORMRESUME);
921			apm_record_event(sc, apm_event);
922			apm_resume();
923			break;
924		    OPMEV_DEBUGMESSAGE(PMEV_CRITRESUME);
925			apm_record_event(sc, apm_event);
926			apm_resume();
927			break;
928		    OPMEV_DEBUGMESSAGE(PMEV_STANDBYRESUME);
929			apm_record_event(sc, apm_event);
930			break;
931		    OPMEV_DEBUGMESSAGE(PMEV_BATTERYLOW);
932			if (apm_record_event(sc, apm_event)) {
933			    apm_battery_low();
934			    apm_suspend(PMST_SUSPEND);
935			}
936			break;
937		    OPMEV_DEBUGMESSAGE(PMEV_POWERSTATECHANGE);
938			apm_record_event(sc, apm_event);
939			break;
940		    OPMEV_DEBUGMESSAGE(PMEV_UPDATETIME);
941			apm_record_event(sc, apm_event);
942			inittodr(0);	/* adjust time to RTC */
943			break;
944		    OPMEV_DEBUGMESSAGE(PMEV_CAPABILITIESCHANGE);
945			apm_record_event(sc, apm_event);
946			break;
947		    case PMEV_NOEVENT:
948			break;
949		    default:
950			printf("Unknown Original APM Event 0x%x\n", apm_event);
951			    break;
952		}
953	} while (apm_event != PMEV_NOEVENT);
954}
955
956/*
957 * Attach APM:
958 *
959 * Initialize APM driver
960 */
961
962static int
963apm_attach(device_t dev)
964{
965	struct apm_softc	*sc = &apm_softc;
966	int			flags;
967	int			drv_version;
968
969	if (resource_int_value("apm", 0, "flags", &flags) != 0)
970		flags = 0;
971
972	if (flags & 0x20)
973		statclock_disable = 1;
974
975	sc->initialized = 0;
976
977	/* Must be externally enabled */
978	sc->active = 0;
979
980	/* Always call HLT in idle loop */
981	sc->always_halt_cpu = 1;
982
983	getenv_int("debug.apm_debug", &apm_debug);
984
985	/* print bootstrap messages */
986	APM_DPRINT("apm: APM BIOS version %04lx\n",  apm_version);
987	APM_DPRINT("apm: Code16 0x%08x, Data 0x%08x\n",
988	    sc->bios.seg.code16.base, sc->bios.seg.data.base);
989	APM_DPRINT("apm: Code entry 0x%08x, Idling CPU %s, Management %s\n",
990	    sc->bios.entry, is_enabled(sc->slow_idle_cpu),
991	    is_enabled(!sc->disabled));
992	APM_DPRINT("apm: CS_limit=0x%x, DS_limit=0x%x\n",
993	    sc->bios.seg.code16.limit, sc->bios.seg.data.limit);
994
995	/*
996         * In one test, apm bios version was 1.02; an attempt to register
997         * a 1.04 driver resulted in a 1.00 connection!  Registering a
998         * 1.02 driver resulted in a 1.02 connection.
999         */
1000	drv_version = apm_version > 0x102 ? 0x102 : apm_version;
1001	for (; drv_version > 0x100; drv_version--)
1002		if (apm_driver_version(drv_version) == 0)
1003			break;
1004	sc->minorversion = ((drv_version & 0x00f0) >>  4) * 10 +
1005		((drv_version & 0x000f) >> 0);
1006	sc->majorversion = ((drv_version & 0xf000) >> 12) * 10 +
1007		((apm_version & 0x0f00) >> 8);
1008
1009	sc->intversion = INTVERSION(sc->majorversion, sc->minorversion);
1010
1011	if (sc->intversion >= INTVERSION(1, 1))
1012		APM_DPRINT("apm: Engaged control %s\n", is_enabled(!sc->disengaged));
1013	device_printf(dev, "found APM BIOS v%ld.%ld, connected at v%d.%d\n",
1014	       ((apm_version & 0xf000) >> 12) * 10 + ((apm_version & 0x0f00) >> 8),
1015	       ((apm_version & 0x00f0) >> 4) * 10 + ((apm_version & 0x000f) >> 0),
1016	       sc->majorversion, sc->minorversion);
1017
1018
1019	APM_DPRINT("apm: Slow Idling CPU %s\n", is_enabled(sc->slow_idle_cpu));
1020	/* enable power management */
1021	if (sc->disabled) {
1022		if (apm_enable_disable_pm(1)) {
1023			APM_DPRINT("apm: *Warning* enable function failed! [%x]\n",
1024			    (sc->bios.r.eax >> 8) & 0xff);
1025		}
1026	}
1027
1028	/* engage power managment (APM 1.1 or later) */
1029	if (sc->intversion >= INTVERSION(1, 1) && sc->disengaged) {
1030		if (apm_engage_disengage_pm(1)) {
1031			APM_DPRINT("apm: *Warning* engage function failed err=[%x]",
1032			    (sc->bios.r.eax >> 8) & 0xff);
1033			APM_DPRINT(" (Docked or using external power?).\n");
1034		}
1035	}
1036
1037	/* Power the system off using APM */
1038	EVENTHANDLER_REGISTER(shutdown_final, apm_power_off, NULL,
1039			      SHUTDOWN_PRI_LAST);
1040
1041	sc->initialized = 1;
1042	sc->suspending = 0;
1043
1044	make_dev(&apm_cdevsw, 0, 0, 5, 0660, "apm");
1045	make_dev(&apm_cdevsw, 8, 0, 5, 0660, "apmctl");
1046	return 0;
1047}
1048
1049static int
1050apmopen(dev_t dev, int flag, int fmt, struct proc *p)
1051{
1052	struct apm_softc *sc = &apm_softc;
1053	int ctl = APMDEV(dev);
1054
1055	if (!sc->initialized)
1056		return (ENXIO);
1057
1058	switch (ctl) {
1059	case APMDEV_CTL:
1060		if (!(flag & FWRITE))
1061			return EINVAL;
1062		if (sc->sc_flags & SCFLAG_OCTL)
1063			return EBUSY;
1064		sc->sc_flags |= SCFLAG_OCTL;
1065		bzero(sc->event_filter, sizeof sc->event_filter);
1066		break;
1067	case APMDEV_NORMAL:
1068		sc->sc_flags |= SCFLAG_ONORMAL;
1069		break;
1070	default:
1071		return ENXIO;
1072		break;
1073	}
1074	return 0;
1075}
1076
1077static int
1078apmclose(dev_t dev, int flag, int fmt, struct proc *p)
1079{
1080	struct apm_softc *sc = &apm_softc;
1081	int ctl = APMDEV(dev);
1082
1083	switch (ctl) {
1084	case APMDEV_CTL:
1085		apm_lastreq_rejected();
1086		sc->sc_flags &= ~SCFLAG_OCTL;
1087		bzero(sc->event_filter, sizeof sc->event_filter);
1088		break;
1089	case APMDEV_NORMAL:
1090		sc->sc_flags &= ~SCFLAG_ONORMAL;
1091		break;
1092	}
1093	if ((sc->sc_flags & SCFLAG_OPEN) == 0) {
1094		sc->event_count = 0;
1095		sc->event_ptr = 0;
1096	}
1097	return 0;
1098}
1099
1100static int
1101apmioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct proc *p)
1102{
1103	struct apm_softc *sc = &apm_softc;
1104	struct apm_bios_arg *args;
1105	int error = 0;
1106	int ret;
1107	int newstate;
1108
1109	if (!sc->initialized)
1110		return (ENXIO);
1111	APM_DPRINT("APM ioctl: cmd = 0x%lx\n", cmd);
1112	switch (cmd) {
1113	case APMIO_SUSPEND:
1114		if (!(flag & FWRITE))
1115			return (EPERM);
1116		if (sc->active)
1117			apm_suspend(PMST_SUSPEND);
1118		else
1119			error = EINVAL;
1120		break;
1121
1122	case APMIO_STANDBY:
1123		if (!(flag & FWRITE))
1124			return (EPERM);
1125		if (sc->active)
1126			apm_suspend(PMST_STANDBY);
1127		else
1128			error = EINVAL;
1129		break;
1130
1131	case APMIO_GETINFO_OLD:
1132		{
1133			struct apm_info info;
1134			apm_info_old_t aiop;
1135
1136			if (apm_get_info(&info))
1137				error = ENXIO;
1138			aiop = (apm_info_old_t)addr;
1139			aiop->ai_major = info.ai_major;
1140			aiop->ai_minor = info.ai_minor;
1141			aiop->ai_acline = info.ai_acline;
1142			aiop->ai_batt_stat = info.ai_batt_stat;
1143			aiop->ai_batt_life = info.ai_batt_life;
1144			aiop->ai_status = info.ai_status;
1145		}
1146		break;
1147	case APMIO_GETINFO:
1148		if (apm_get_info((apm_info_t)addr))
1149			error = ENXIO;
1150		break;
1151	case APMIO_GETPWSTATUS:
1152		if (apm_get_pwstatus((apm_pwstatus_t)addr))
1153			error = ENXIO;
1154		break;
1155	case APMIO_ENABLE:
1156		if (!(flag & FWRITE))
1157			return (EPERM);
1158		apm_event_enable();
1159		break;
1160	case APMIO_DISABLE:
1161		if (!(flag & FWRITE))
1162			return (EPERM);
1163		apm_event_disable();
1164		break;
1165	case APMIO_HALTCPU:
1166		if (!(flag & FWRITE))
1167			return (EPERM);
1168		apm_halt_cpu();
1169		break;
1170	case APMIO_NOTHALTCPU:
1171		if (!(flag & FWRITE))
1172			return (EPERM);
1173		apm_not_halt_cpu();
1174		break;
1175	case APMIO_DISPLAY:
1176		if (!(flag & FWRITE))
1177			return (EPERM);
1178		newstate = *(int *)addr;
1179		if (apm_display(newstate))
1180			error = ENXIO;
1181		break;
1182	case APMIO_BIOS:
1183		if (!(flag & FWRITE))
1184			return (EPERM);
1185		/* XXX compatibility with the old interface */
1186		args = (struct apm_bios_arg *)addr;
1187		sc->bios.r.eax = args->eax;
1188		sc->bios.r.ebx = args->ebx;
1189		sc->bios.r.ecx = args->ecx;
1190		sc->bios.r.edx = args->edx;
1191		sc->bios.r.esi = args->esi;
1192		sc->bios.r.edi = args->edi;
1193		if ((ret = apm_bioscall())) {
1194			/*
1195			 * Return code 1 means bios call was unsuccessful.
1196			 * Error code is stored in %ah.
1197			 * Return code -1 means bios call was unsupported
1198			 * in the APM BIOS version.
1199			 */
1200			if (ret == -1) {
1201				error = EINVAL;
1202			}
1203		} else {
1204			/*
1205			 * Return code 0 means bios call was successful.
1206			 * We need only %al and can discard %ah.
1207			 */
1208			sc->bios.r.eax &= 0xff;
1209		}
1210		args->eax = sc->bios.r.eax;
1211		args->ebx = sc->bios.r.ebx;
1212		args->ecx = sc->bios.r.ecx;
1213		args->edx = sc->bios.r.edx;
1214		args->esi = sc->bios.r.esi;
1215		args->edi = sc->bios.r.edi;
1216		break;
1217	default:
1218		error = EINVAL;
1219		break;
1220	}
1221
1222	/* for /dev/apmctl */
1223	if (APMDEV(dev) == APMDEV_CTL) {
1224		struct apm_event_info *evp;
1225		int i;
1226
1227		error = 0;
1228		switch (cmd) {
1229		case APMIO_NEXTEVENT:
1230			if (!sc->event_count) {
1231				error = EAGAIN;
1232			} else {
1233				evp = (struct apm_event_info *)addr;
1234				i = sc->event_ptr + APM_NEVENTS - sc->event_count;
1235				i %= APM_NEVENTS;
1236				*evp = sc->event_list[i];
1237				sc->event_count--;
1238			}
1239			break;
1240		case APMIO_REJECTLASTREQ:
1241			if (apm_lastreq_rejected()) {
1242				error = EINVAL;
1243			}
1244			break;
1245		default:
1246			error = EINVAL;
1247			break;
1248		}
1249	}
1250
1251	return error;
1252}
1253
1254static int
1255apmwrite(dev_t dev, struct uio *uio, int ioflag)
1256{
1257	struct apm_softc *sc = &apm_softc;
1258	u_int event_type;
1259	int error;
1260	u_char enabled;
1261
1262	if (APMDEV(dev) != APMDEV_CTL)
1263		return(ENODEV);
1264	if (uio->uio_resid != sizeof(u_int))
1265		return(E2BIG);
1266
1267	if ((error = uiomove((caddr_t)&event_type, sizeof(u_int), uio)))
1268		return(error);
1269
1270	if (event_type < 0 || event_type >= APM_NPMEV)
1271		return(EINVAL);
1272
1273	if (sc->event_filter[event_type] == 0) {
1274		enabled = 1;
1275	} else {
1276		enabled = 0;
1277	}
1278	sc->event_filter[event_type] = enabled;
1279	APM_DPRINT("apmwrite: event 0x%x %s\n", event_type, is_enabled(enabled));
1280
1281	return uio->uio_resid;
1282}
1283
1284static int
1285apmpoll(dev_t dev, int events, struct proc *p)
1286{
1287	struct apm_softc *sc = &apm_softc;
1288	int revents = 0;
1289
1290	if (events & (POLLIN | POLLRDNORM)) {
1291		if (sc->event_count) {
1292			revents |= events & (POLLIN | POLLRDNORM);
1293		} else {
1294			selrecord(p, &sc->sc_rsel);
1295		}
1296	}
1297
1298	return (revents);
1299}
1300
1301static device_method_t apm_methods[] = {
1302	/* Device interface */
1303	DEVMETHOD(device_identify,	apm_identify),
1304	DEVMETHOD(device_probe,		apm_probe),
1305	DEVMETHOD(device_attach,	apm_attach),
1306
1307	{ 0, 0 }
1308};
1309
1310static driver_t apm_driver = {
1311	"apm",
1312	apm_methods,
1313	1,			/* no softc (XXX) */
1314};
1315
1316static devclass_t apm_devclass;
1317
1318DRIVER_MODULE(apm, nexus, apm_driver, apm_devclass, 0, 0);
1319