apm.c revision 139790
1193326Sed/*-
2193326Sed * APM (Advanced Power Management) BIOS Device Driver
3193326Sed *
4193326Sed * Copyright (c) 1994 UKAI, Fumitoshi.
5193326Sed * Copyright (c) 1994-1995 by HOSOKAWA, Tatsumi <hosokawa@jp.FreeBSD.org>
6193326Sed * Copyright (c) 1996 Nate Williams <nate@FreeBSD.org>
7193326Sed * Copyright (c) 1997 Poul-Henning Kamp <phk@FreeBSD.org>
8193326Sed *
9193326Sed * This software may be used, modified, copied, and distributed, in
10221345Sdim * both source and binary form provided that the above copyright and
11193326Sed * these terms are retained. Under no circumstances is the author
12193326Sed * responsible for the proper functioning of this software, nor does
13193326Sed * the author assume any responsibility for damages incurred with its
14221345Sdim * use.
15221345Sdim *
16243830Sdim * Sep, 1994	Implemented on FreeBSD 1.1.5.1R (Toshiba AVS001WD)
17221345Sdim */
18223017Sdim
19221345Sdim#include <sys/cdefs.h>
20239462Sdim__FBSDID("$FreeBSD: head/sys/i386/bios/apm.c 139790 2005-01-06 22:18:23Z imp $");
21221345Sdim
22221345Sdim#include <sys/param.h>
23221345Sdim#include <sys/systm.h>
24221345Sdim#include <sys/bus.h>
25193326Sed#include <sys/conf.h>
26243830Sdim#include <sys/eventhandler.h>
27234353Sdim#include <sys/fcntl.h>
28193326Sed#include <sys/kernel.h>
29193326Sed#include <sys/module.h>
30193326Sed#include <sys/poll.h>
31239462Sdim#include <sys/power.h>
32239462Sdim#include <sys/reboot.h>
33221345Sdim#include <sys/selinfo.h>
34221345Sdim#include <sys/signalvar.h>
35221345Sdim#include <sys/sysctl.h>
36221345Sdim#include <sys/syslog.h>
37221345Sdim#include <sys/time.h>
38221345Sdim#include <sys/uio.h>
39221345Sdim
40221345Sdim#include <machine/apm_bios.h>
41221345Sdim#include <machine/clock.h>
42193326Sed#include <machine/endian.h>
43221345Sdim#include <machine/pc/bios.h>
44221345Sdim#include <machine/cpufunc.h>
45221345Sdim#include <machine/segments.h>
46221345Sdim#include <machine/stdarg.h>
47193326Sed#include <machine/vm86.h>
48221345Sdim
49221345Sdim#include <machine/bus.h>
50221345Sdim#include <machine/resource.h>
51221345Sdim#include <sys/rman.h>
52221345Sdim
53221345Sdim#include <vm/vm.h>
54221345Sdim#include <vm/pmap.h>
55221345Sdim#include <vm/vm_param.h>
56221345Sdim
57221345Sdim#include <i386/bios/apm.h>
58221345Sdim
59221345Sdim/* Used by the apm_saver screen saver module */
60221345Sdimint apm_display(int newstate);
61221345Sdimstruct apm_softc apm_softc;
62221345Sdim
63193326Sedstatic void apm_resume(void);
64221345Sdimstatic int apm_bioscall(void);
65221345Sdimstatic int apm_check_function_supported(u_int version, u_int func);
66221345Sdim
67221345Sdimstatic int apm_pm_func(u_long, void*, ...);
68221345Sdim
69221345Sdimstatic u_long	apm_version;
70221345Sdim
71221345Sdimint	apm_evindex;
72221345Sdim
73221345Sdim#define	SCFLAG_ONORMAL	0x0000001
74193326Sed#define	SCFLAG_OCTL	0x0000002
75221345Sdim#define	SCFLAG_OPEN	(SCFLAG_ONORMAL|SCFLAG_OCTL)
76221345Sdim
77221345Sdim#define APMDEV(dev)	(minor(dev)&0x0f)
78221345Sdim#define APMDEV_NORMAL	0
79221345Sdim#define APMDEV_CTL	8
80221345Sdim
81198092Srdivacky#ifdef PC98
82221345Sdimextern int bios32_apm98(struct bios_regs *, u_int, u_short);
83221345Sdim
84221345Sdim/* PC98's SMM definition */
85198092Srdivacky#define	APM_NECSMM_PORT		0x6b8e
86221345Sdim#define	APM_NECSMM_PORTSZ	1
87221345Sdim#define	APM_NECSMM_EN		0x10
88221345Sdimstatic __inline void apm_enable_smm(struct apm_softc *);
89221345Sdimstatic __inline void apm_disable_smm(struct apm_softc *);
90221345Sdimint apm_necsmm_addr;
91221345Sdimu_int32_t apm_necsmm_mask;
92193326Sed#endif
93221345Sdim
94221345Sdimstatic struct apmhook	*hook[NAPM_HOOK];		/* XXX */
95193326Sed
96221345Sdim#define is_enabled(foo) ((foo) ? "enabled" : "disabled")
97221345Sdim
98221345Sdim/* Map version number to integer (keeps ordering of version numbers) */
99193326Sed#define INTVERSION(major, minor)	((major)*100 + (minor))
100193326Sed
101198092Srdivackystatic struct callout_handle apm_timeout_ch =
102243830Sdim    CALLOUT_HANDLE_INITIALIZER(&apm_timeout_ch);
103221345Sdim
104221345Sdimstatic timeout_t apm_timeout;
105221345Sdimstatic d_open_t apmopen;
106243830Sdimstatic d_close_t apmclose;
107221345Sdimstatic d_write_t apmwrite;
108221345Sdimstatic d_ioctl_t apmioctl;
109193326Sedstatic d_poll_t apmpoll;
110221345Sdim
111239462Sdimstatic struct cdevsw apm_cdevsw = {
112221345Sdim	.d_version =	D_VERSION,
113221345Sdim	.d_flags =	D_NEEDGIANT,
114221345Sdim	.d_open =	apmopen,
115239462Sdim	.d_close =	apmclose,
116243830Sdim	.d_write =	apmwrite,
117239462Sdim	.d_ioctl =	apmioctl,
118198092Srdivacky	.d_poll =	apmpoll,
119239462Sdim	.d_name =	"apm",
120221345Sdim};
121221345Sdim
122221345Sdimstatic int apm_suspend_delay = 1;
123221345Sdimstatic int apm_standby_delay = 1;
124221345Sdimstatic int apm_swab_batt_minutes = 0;
125193326Sedstatic int apm_debug = 0;
126226633Sdim
127221345Sdim#define APM_DPRINT(args...) do	{					\
128221345Sdim	if (apm_debug) {						\
129221345Sdim		printf(args);						\
130239462Sdim	}								\
131239462Sdim} while (0)
132239462Sdim
133239462SdimSYSCTL_INT(_machdep, OID_AUTO, apm_suspend_delay, CTLFLAG_RW, &apm_suspend_delay, 1, "");
134239462SdimSYSCTL_INT(_machdep, OID_AUTO, apm_standby_delay, CTLFLAG_RW, &apm_standby_delay, 1, "");
135239462SdimSYSCTL_INT(_debug, OID_AUTO, apm_debug, CTLFLAG_RW, &apm_debug, 0, "");
136239462Sdim
137221345SdimTUNABLE_INT("machdep.apm_swab_batt_minutes", &apm_swab_batt_minutes);
138221345SdimSYSCTL_INT(_machdep, OID_AUTO, apm_swab_batt_minutes, CTLFLAG_RW,
139198092Srdivacky	   &apm_swab_batt_minutes, 0, "Byte swap battery time value.");
140239462Sdim
141198092Srdivacky#ifdef PC98
142221345Sdimstatic __inline void
143221345Sdimapm_enable_smm(sc)
144239462Sdim	struct apm_softc *sc;
145239462Sdim{
146239462Sdim	bus_space_tag_t iot = sc->sc_iot;
147239462Sdim	bus_space_handle_t ioh = sc->sc_ioh;
148239462Sdim	if (apm_necsmm_addr != 0)
149239462Sdim		bus_space_write_1(iot, ioh, 0,
150239462Sdim			  (bus_space_read_1(iot, ioh, 0) | ~apm_necsmm_mask));
151243830Sdim}
152221345Sdim
153198092Srdivackystatic __inline void
154239462Sdimapm_disable_smm(sc)
155221345Sdim	struct apm_softc *sc;
156221345Sdim{
157221345Sdim	bus_space_tag_t iot = sc->sc_iot;
158221345Sdim	bus_space_handle_t ioh = sc->sc_ioh;
159221345Sdim	if (apm_necsmm_addr != 0)
160221345Sdim		bus_space_write_1(iot, ioh, 0,
161221345Sdim			  (bus_space_read_1(iot, ioh, 0) & apm_necsmm_mask));
162221345Sdim}
163239462Sdim#endif
164226633Sdim
165239462Sdim/*
166239462Sdim * return  0 if the function successfull,
167239462Sdim * return  1 if the function unsuccessfull,
168226633Sdim * return -1 if the function unsupported.
169198092Srdivacky */
170226633Sdimstatic int
171226633Sdimapm_bioscall(void)
172226633Sdim{
173226633Sdim	struct apm_softc *sc = &apm_softc;
174226633Sdim	int errno = 0;
175226633Sdim	u_int apm_func = sc->bios.r.eax & 0xff;
176226633Sdim
177226633Sdim	if (!apm_check_function_supported(sc->intversion, apm_func)) {
178221345Sdim		APM_DPRINT("apm_bioscall: function 0x%x is not supported in v%d.%d\n",
179239462Sdim		    apm_func, sc->majorversion, sc->minorversion);
180221345Sdim		return (-1);
181221345Sdim	}
182221345Sdim
183239462Sdim	sc->bios_busy = 1;
184221345Sdim#ifdef	PC98
185221345Sdim	set_bios_selectors(&sc->bios.seg, BIOSCODE_FLAG | BIOSDATA_FLAG);
186221345Sdim	if (bios32_apm98(&sc->bios.r, sc->bios.entry,
187221345Sdim	    GSEL(GBIOSCODE32_SEL, SEL_KPL)) != 0)
188193326Sed		return 1;
189221345Sdim#else
190221345Sdim	if (sc->connectmode == APM_PROT32CONNECT) {
191221345Sdim		set_bios_selectors(&sc->bios.seg,
192193326Sed				   BIOSCODE_FLAG | BIOSDATA_FLAG);
193221345Sdim		errno = bios32(&sc->bios.r,
194221345Sdim			       sc->bios.entry, GSEL(GBIOSCODE32_SEL, SEL_KPL));
195221345Sdim	} else {
196221345Sdim		errno = bios16(&sc->bios, NULL);
197221345Sdim	}
198193326Sed#endif
199221345Sdim	sc->bios_busy = 0;
200221345Sdim	return (errno);
201221345Sdim}
202221345Sdim
203221345Sdim/* check whether APM function is supported (1)  or not (0). */
204221345Sdimstatic int
205226633Sdimapm_check_function_supported(u_int version, u_int func)
206221345Sdim{
207221345Sdim	/* except driver version */
208221345Sdim	if (func == APM_DRVVERSION) {
209221345Sdim		return (1);
210221345Sdim	}
211221345Sdim#ifdef PC98
212221345Sdim	if (func == APM_GETPWSTATUS) {
213193326Sed		return (1);
214193326Sed	}
215221345Sdim#endif
216224145Sdim
217221345Sdim	switch (version) {
218221345Sdim	case INTVERSION(1, 0):
219224145Sdim		if (func > APM_GETPMEVENT) {
220224145Sdim			return (0); /* not supported */
221224145Sdim		}
222224145Sdim		break;
223224145Sdim	case INTVERSION(1, 1):
224193326Sed		if (func > APM_ENGAGEDISENGAGEPM &&
225224145Sdim		    func < APM_OEMFUNC) {
226224145Sdim			return (0); /* not supported */
227224145Sdim		}
228224145Sdim		break;
229224145Sdim	case INTVERSION(1, 2):
230224145Sdim		break;
231224145Sdim	}
232193326Sed
233198092Srdivacky	return (1); /* supported */
234221345Sdim}
235221345Sdim
236221345Sdim/* enable/disable power management */
237221345Sdimstatic int
238221345Sdimapm_enable_disable_pm(int enable)
239221345Sdim{
240221345Sdim	struct apm_softc *sc = &apm_softc;
241193326Sed
242193326Sed	sc->bios.r.eax = (APM_BIOS << 8) | APM_ENABLEDISABLEPM;
243221345Sdim
244239462Sdim	if (sc->intversion >= INTVERSION(1, 1))
245221345Sdim		sc->bios.r.ebx  = PMDV_ALLDEV;
246198092Srdivacky	else
247221345Sdim		sc->bios.r.ebx  = 0xffff;	/* APM version 1.0 only */
248221345Sdim	sc->bios.r.ecx  = enable;
249221345Sdim	sc->bios.r.edx = 0;
250221345Sdim	return (apm_bioscall());
251221345Sdim}
252239462Sdim
253239462Sdim/* register driver version (APM 1.1 or later) */
254221345Sdimstatic int
255221345Sdimapm_driver_version(int version)
256221345Sdim{
257239462Sdim	struct apm_softc *sc = &apm_softc;
258239462Sdim
259239462Sdim	sc->bios.r.eax = (APM_BIOS << 8) | APM_DRVVERSION;
260239462Sdim	sc->bios.r.ebx  = 0x0;
261239462Sdim	sc->bios.r.ecx  = version;
262239462Sdim	sc->bios.r.edx = 0;
263239462Sdim
264239462Sdim	if (apm_bioscall() == 0 && sc->bios.r.eax == version)
265239462Sdim		return (0);
266239462Sdim
267239462Sdim	/* Some old BIOSes don't return the connection version in %ax. */
268239462Sdim	if (sc->bios.r.eax == ((APM_BIOS << 8) | APM_DRVVERSION))
269239462Sdim		return (0);
270239462Sdim
271239462Sdim	return (1);
272239462Sdim}
273239462Sdim
274239462Sdim/* engage/disengage power management (APM 1.1 or later) */
275239462Sdimstatic int
276239462Sdimapm_engage_disengage_pm(int engage)
277239462Sdim{
278239462Sdim	struct apm_softc *sc = &apm_softc;
279239462Sdim
280239462Sdim	sc->bios.r.eax = (APM_BIOS << 8) | APM_ENGAGEDISENGAGEPM;
281239462Sdim	sc->bios.r.ebx = PMDV_ALLDEV;
282239462Sdim	sc->bios.r.ecx = engage;
283239462Sdim	sc->bios.r.edx = 0;
284239462Sdim	return (apm_bioscall());
285239462Sdim}
286239462Sdim
287239462Sdim/* get PM event */
288239462Sdimstatic u_int
289239462Sdimapm_getevent(void)
290239462Sdim{
291239462Sdim	struct apm_softc *sc = &apm_softc;
292239462Sdim
293239462Sdim	sc->bios.r.eax = (APM_BIOS << 8) | APM_GETPMEVENT;
294239462Sdim
295239462Sdim	sc->bios.r.ebx = 0;
296239462Sdim	sc->bios.r.ecx = 0;
297239462Sdim	sc->bios.r.edx = 0;
298239462Sdim	if (apm_bioscall())
299239462Sdim		return (PMEV_NOEVENT);
300239462Sdim	return (sc->bios.r.ebx & 0xffff);
301239462Sdim}
302239462Sdim
303239462Sdim/* suspend entire system */
304239462Sdimstatic int
305239462Sdimapm_suspend_system(int state)
306239462Sdim{
307239462Sdim	struct apm_softc *sc = &apm_softc;
308239462Sdim
309239462Sdim	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
310239462Sdim	sc->bios.r.ebx = PMDV_ALLDEV;
311239462Sdim	sc->bios.r.ecx = state;
312239462Sdim	sc->bios.r.edx = 0;
313239462Sdim
314239462Sdim#ifdef PC98
315239462Sdim	apm_disable_smm(sc);
316239462Sdim#endif
317239462Sdim	if (apm_bioscall()) {
318239462Sdim 		printf("Entire system suspend failure: errcode = %d\n",
319239462Sdim		       0xff & (sc->bios.r.eax >> 8));
320239462Sdim 		return 1;
321239462Sdim 	}
322239462Sdim#ifdef PC98
323239462Sdim	apm_enable_smm(sc);
324239462Sdim#endif
325239462Sdim 	return 0;
326239462Sdim}
327239462Sdim
328239462Sdim/* Display control */
329239462Sdim/*
330239462Sdim * Experimental implementation: My laptop machine can't handle this function
331239462Sdim * If your laptop can control the display via APM, please inform me.
332239462Sdim *                            HOSOKAWA, Tatsumi <hosokawa@jp.FreeBSD.org>
333239462Sdim */
334239462Sdimint
335239462Sdimapm_display(int newstate)
336239462Sdim{
337239462Sdim	struct apm_softc *sc = &apm_softc;
338239462Sdim
339239462Sdim	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
340239462Sdim	sc->bios.r.ebx = PMDV_DISP0;
341239462Sdim	sc->bios.r.ecx = newstate ? PMST_APMENABLED:PMST_SUSPEND;
342239462Sdim	sc->bios.r.edx = 0;
343239462Sdim	if (apm_bioscall() == 0) {
344239462Sdim		return 0;
345239462Sdim 	}
346239462Sdim
347239462Sdim	/* If failed, then try to blank all display devices instead. */
348239462Sdim	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
349239462Sdim	sc->bios.r.ebx = PMDV_DISPALL;	/* all display devices */
350239462Sdim	sc->bios.r.ecx = newstate ? PMST_APMENABLED:PMST_SUSPEND;
351239462Sdim	sc->bios.r.edx = 0;
352239462Sdim	if (apm_bioscall() == 0) {
353239462Sdim		return 0;
354239462Sdim 	}
355239462Sdim 	printf("Display off failure: errcode = %d\n",
356239462Sdim	       0xff & (sc->bios.r.eax >> 8));
357239462Sdim 	return 1;
358239462Sdim}
359239462Sdim
360239462Sdim/*
361239462Sdim * Turn off the entire system.
362239462Sdim */
363239462Sdimstatic void
364239462Sdimapm_power_off(void *junk, int howto)
365239462Sdim{
366239462Sdim	struct apm_softc *sc = &apm_softc;
367239462Sdim
368239462Sdim	/* Not halting powering off, or not active */
369239462Sdim	if (!(howto & RB_POWEROFF) || !apm_softc.active)
370239462Sdim		return;
371239462Sdim	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
372239462Sdim	sc->bios.r.ebx = PMDV_ALLDEV;
373239462Sdim	sc->bios.r.ecx = PMST_OFF;
374239462Sdim	sc->bios.r.edx = 0;
375239462Sdim	(void) apm_bioscall();
376239462Sdim}
377239462Sdim
378239462Sdim/* APM Battery low handler */
379239462Sdimstatic void
380239462Sdimapm_battery_low(void)
381239462Sdim{
382239462Sdim	printf("\007\007 * * * BATTERY IS LOW * * * \007\007");
383239462Sdim}
384239462Sdim
385239462Sdim/* APM hook manager */
386239462Sdimstatic struct apmhook *
387239462Sdimapm_add_hook(struct apmhook **list, struct apmhook *ah)
388239462Sdim{
389239462Sdim	int s;
390239462Sdim	struct apmhook *p, *prev;
391239462Sdim
392239462Sdim	APM_DPRINT("Add hook \"%s\"\n", ah->ah_name);
393239462Sdim
394239462Sdim	s = splhigh();
395239462Sdim	if (ah == NULL)
396239462Sdim		panic("illegal apm_hook!");
397239462Sdim	prev = NULL;
398239462Sdim	for (p = *list; p != NULL; prev = p, p = p->ah_next)
399239462Sdim		if (p->ah_order > ah->ah_order)
400239462Sdim			break;
401239462Sdim
402239462Sdim	if (prev == NULL) {
403239462Sdim		ah->ah_next = *list;
404226633Sdim		*list = ah;
405221345Sdim	} else {
406221345Sdim		ah->ah_next = prev->ah_next;
407239462Sdim		prev->ah_next = ah;
408234353Sdim	}
409239462Sdim	splx(s);
410243830Sdim	return ah;
411221345Sdim}
412239462Sdim
413221345Sdimstatic void
414221345Sdimapm_del_hook(struct apmhook **list, struct apmhook *ah)
415239462Sdim{
416239462Sdim	int s;
417226633Sdim	struct apmhook *p, *prev;
418239462Sdim
419243830Sdim	s = splhigh();
420243830Sdim	prev = NULL;
421221345Sdim	for (p = *list; p != NULL; prev = p, p = p->ah_next)
422239462Sdim		if (p == ah)
423239462Sdim			goto deleteit;
424243830Sdim	panic("Tried to delete unregistered apm_hook.");
425221345Sdim	goto nosuchnode;
426239462Sdimdeleteit:
427243830Sdim	if (prev != NULL)
428221345Sdim		prev->ah_next = p->ah_next;
429243830Sdim	else
430243830Sdim		*list = p->ah_next;
431239462Sdimnosuchnode:
432221345Sdim	splx(s);
433221345Sdim}
434193326Sed
435193326Sed
436239462Sdim/* APM driver calls some functions automatically */
437239462Sdimstatic void
438239462Sdimapm_execute_hook(struct apmhook *list)
439239462Sdim{
440239462Sdim	struct apmhook *p;
441239462Sdim
442239462Sdim	for (p = list; p != NULL; p = p->ah_next) {
443239462Sdim		APM_DPRINT("Execute APM hook \"%s.\"\n", p->ah_name);
444239462Sdim		if ((*(p->ah_fun))(p->ah_arg))
445239462Sdim			printf("Warning: APM hook \"%s\" failed", p->ah_name);
446239462Sdim	}
447239462Sdim}
448239462Sdim
449239462Sdim
450239462Sdim/* establish an apm hook */
451239462Sdimstruct apmhook *
452239462Sdimapm_hook_establish(int apmh, struct apmhook *ah)
453239462Sdim{
454239462Sdim	if (apmh < 0 || apmh >= NAPM_HOOK)
455239462Sdim		return NULL;
456239462Sdim
457239462Sdim	return apm_add_hook(&hook[apmh], ah);
458239462Sdim}
459239462Sdim
460239462Sdim/* disestablish an apm hook */
461239462Sdimvoid
462239462Sdimapm_hook_disestablish(int apmh, struct apmhook *ah)
463239462Sdim{
464239462Sdim	if (apmh < 0 || apmh >= NAPM_HOOK)
465239462Sdim		return;
466239462Sdim
467239462Sdim	apm_del_hook(&hook[apmh], ah);
468239462Sdim}
469239462Sdim
470239462Sdimstatic int apm_record_event(struct apm_softc *, u_int);
471239462Sdimstatic void apm_processevent(void);
472239462Sdim
473239462Sdimstatic u_int apm_op_inprog = 0;
474239462Sdim
475239462Sdimstatic void
476239462Sdimapm_do_suspend(void)
477239462Sdim{
478239462Sdim	struct apm_softc *sc = &apm_softc;
479239462Sdim	int error;
480239462Sdim
481239462Sdim	if (!sc)
482239462Sdim		return;
483239462Sdim
484239462Sdim	apm_op_inprog = 0;
485239462Sdim	sc->suspends = sc->suspend_countdown = 0;
486239462Sdim
487239462Sdim	if (sc->initialized) {
488239462Sdim		error = DEVICE_SUSPEND(root_bus);
489239462Sdim		if (error) {
490239462Sdim			DEVICE_RESUME(root_bus);
491239462Sdim		} else {
492239462Sdim			apm_execute_hook(hook[APM_HOOK_SUSPEND]);
493239462Sdim			if (apm_suspend_system(PMST_SUSPEND) == 0) {
494239462Sdim				sc->suspending = 1;
495239462Sdim				apm_processevent();
496239462Sdim			} else {
497239462Sdim				/* Failure, 'resume' the system again */
498239462Sdim				apm_execute_hook(hook[APM_HOOK_RESUME]);
499239462Sdim				DEVICE_RESUME(root_bus);
500239462Sdim			}
501239462Sdim		}
502239462Sdim	}
503239462Sdim}
504239462Sdim
505239462Sdimstatic void
506239462Sdimapm_do_standby(void)
507239462Sdim{
508239462Sdim	struct apm_softc *sc = &apm_softc;
509239462Sdim
510239462Sdim	if (!sc)
511239462Sdim		return;
512239462Sdim
513239462Sdim	apm_op_inprog = 0;
514239462Sdim	sc->standbys = sc->standby_countdown = 0;
515239462Sdim
516239462Sdim	if (sc->initialized) {
517239462Sdim		/*
518239462Sdim		 * As far as standby, we don't need to execute
519239462Sdim		 * all of suspend hooks.
520239462Sdim		 */
521239462Sdim		if (apm_suspend_system(PMST_STANDBY) == 0)
522239462Sdim			apm_processevent();
523239462Sdim	}
524239462Sdim}
525226633Sdim
526226633Sdimstatic void
527239462Sdimapm_lastreq_notify(void)
528239462Sdim{
529239462Sdim	struct apm_softc *sc = &apm_softc;
530239462Sdim
531239462Sdim	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
532239462Sdim	sc->bios.r.ebx = PMDV_ALLDEV;
533239462Sdim	sc->bios.r.ecx = PMST_LASTREQNOTIFY;
534239462Sdim	sc->bios.r.edx = 0;
535239462Sdim	apm_bioscall();
536239462Sdim}
537239462Sdim
538239462Sdimstatic int
539239462Sdimapm_lastreq_rejected(void)
540239462Sdim{
541239462Sdim	struct apm_softc *sc = &apm_softc;
542239462Sdim
543239462Sdim	if (apm_op_inprog == 0) {
544239462Sdim		return 1;	/* no operation in progress */
545239462Sdim	}
546239462Sdim
547239462Sdim	sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE;
548239462Sdim	sc->bios.r.ebx = PMDV_ALLDEV;
549239462Sdim	sc->bios.r.ecx = PMST_LASTREQREJECT;
550239462Sdim	sc->bios.r.edx = 0;
551239462Sdim
552239462Sdim	if (apm_bioscall()) {
553239462Sdim		APM_DPRINT("apm_lastreq_rejected: failed\n");
554239462Sdim		return 1;
555239462Sdim	}
556239462Sdim	apm_op_inprog = 0;
557239462Sdim	return 0;
558239462Sdim}
559239462Sdim
560239462Sdim/*
561239462Sdim * Public interface to the suspend/resume:
562239462Sdim *
563239462Sdim * Execute suspend and resume hook before and after sleep, respectively.
564239462Sdim *
565239462Sdim */
566239462Sdim
567239462Sdimvoid
568239462Sdimapm_suspend(int state)
569226633Sdim{
570239462Sdim	struct apm_softc *sc = &apm_softc;
571226633Sdim
572226633Sdim	if (!sc->initialized)
573239462Sdim		return;
574239462Sdim
575239462Sdim	switch (state) {
576239462Sdim	case PMST_SUSPEND:
577239462Sdim		if (sc->suspends)
578239462Sdim			return;
579193326Sed		sc->suspends++;
580198092Srdivacky		sc->suspend_countdown = apm_suspend_delay;
581239462Sdim		break;
582193326Sed	case PMST_STANDBY:
583239462Sdim		if (sc->standbys)
584239462Sdim			return;
585239462Sdim		sc->standbys++;
586239462Sdim		sc->standby_countdown = apm_standby_delay;
587193326Sed		break;
588221345Sdim	default:
589198092Srdivacky		printf("apm_suspend: Unknown Suspend state 0x%x\n", state);
590221345Sdim		return;
591221345Sdim	}
592221345Sdim
593221345Sdim	apm_op_inprog++;
594221345Sdim	apm_lastreq_notify();
595221345Sdim}
596221345Sdim
597221345Sdimstatic void
598221345Sdimapm_resume(void)
599221345Sdim{
600221345Sdim	struct apm_softc *sc = &apm_softc;
601239462Sdim
602221345Sdim	if (!sc)
603193326Sed		return;
604198092Srdivacky
605239462Sdim	if (sc->suspending == 0)
606243830Sdim		return;
607243830Sdim
608243830Sdim	sc->suspending = 0;
609243830Sdim	if (sc->initialized) {
610243830Sdim		apm_execute_hook(hook[APM_HOOK_RESUME]);
611243830Sdim		DEVICE_RESUME(root_bus);
612243830Sdim	}
613243830Sdim}
614243830Sdim
615243830Sdim
616243830Sdim/* get power status per battery */
617243830Sdimstatic int
618243830Sdimapm_get_pwstatus(apm_pwstatus_t app)
619243830Sdim{
620243830Sdim	struct apm_softc *sc = &apm_softc;
621243830Sdim
622243830Sdim	if (app->ap_device != PMDV_ALLDEV &&
623243830Sdim	    (app->ap_device < PMDV_BATT0 || app->ap_device > PMDV_BATT_ALL))
624243830Sdim		return 1;
625243830Sdim
626226633Sdim	sc->bios.r.eax = (APM_BIOS << 8) | APM_GETPWSTATUS;
627226633Sdim	sc->bios.r.ebx = app->ap_device;
628239462Sdim	sc->bios.r.ecx = 0;
629239462Sdim	sc->bios.r.edx = 0xffff;	/* default to unknown battery time */
630239462Sdim
631239462Sdim	if (apm_bioscall())
632239462Sdim		return 1;
633239462Sdim
634239462Sdim	app->ap_acline    = (sc->bios.r.ebx >> 8) & 0xff;
635239462Sdim	app->ap_batt_stat = sc->bios.r.ebx & 0xff;
636239462Sdim	app->ap_batt_flag = (sc->bios.r.ecx >> 8) & 0xff;
637239462Sdim	app->ap_batt_life = sc->bios.r.ecx & 0xff;
638239462Sdim	sc->bios.r.edx &= 0xffff;
639239462Sdim	if (apm_swab_batt_minutes)
640239462Sdim		sc->bios.r.edx = __bswap16(sc->bios.r.edx) | 0x8000;
641239462Sdim	if (sc->bios.r.edx == 0xffff)	/* Time is unknown */
642221345Sdim		app->ap_batt_time = -1;
643221345Sdim	else if (sc->bios.r.edx & 0x8000)	/* Time is in minutes */
644193326Sed		app->ap_batt_time = (sc->bios.r.edx & 0x7fff) * 60;
645239462Sdim	else				/* Time is in seconds */
646239462Sdim		app->ap_batt_time = sc->bios.r.edx;
647239462Sdim
648239462Sdim	return 0;
649239462Sdim}
650221345Sdim
651221345Sdim
652198092Srdivacky/* get APM information */
653239462Sdimstatic int
654239462Sdimapm_get_info(apm_info_t aip)
655239462Sdim{
656239462Sdim	struct apm_softc *sc = &apm_softc;
657239462Sdim	struct apm_pwstatus aps;
658239462Sdim
659239462Sdim	bzero(&aps, sizeof(aps));
660239462Sdim	aps.ap_device = PMDV_ALLDEV;
661239462Sdim	if (apm_get_pwstatus(&aps))
662239462Sdim		return 1;
663239462Sdim
664239462Sdim	aip->ai_infoversion = 1;
665239462Sdim	aip->ai_acline      = aps.ap_acline;
666239462Sdim	aip->ai_batt_stat   = aps.ap_batt_stat;
667239462Sdim	aip->ai_batt_life   = aps.ap_batt_life;
668239462Sdim	aip->ai_batt_time   = aps.ap_batt_time;
669239462Sdim	aip->ai_major       = (u_int)sc->majorversion;
670239462Sdim	aip->ai_minor       = (u_int)sc->minorversion;
671239462Sdim	aip->ai_status      = (u_int)sc->active;
672239462Sdim
673239462Sdim	sc->bios.r.eax = (APM_BIOS << 8) | APM_GETCAPABILITIES;
674239462Sdim	sc->bios.r.ebx = 0;
675239462Sdim	sc->bios.r.ecx = 0;
676239462Sdim	sc->bios.r.edx = 0;
677239462Sdim	if (apm_bioscall()) {
678239462Sdim		aip->ai_batteries = 0xffffffff;	/* Unknown */
679239462Sdim		aip->ai_capabilities = 0xff00; /* Unknown, with no bits set */
680239462Sdim	} else {
681239462Sdim		aip->ai_batteries = sc->bios.r.ebx & 0xff;
682239462Sdim		aip->ai_capabilities = sc->bios.r.ecx & 0xff;
683239462Sdim	}
684239462Sdim
685221345Sdim	bzero(aip->ai_spare, sizeof aip->ai_spare);
686221345Sdim
687221345Sdim	return 0;
688193326Sed}
689198092Srdivacky
690243830Sdim
691243830Sdim/* inform APM BIOS that CPU is idle */
692243830Sdimvoid
693243830Sdimapm_cpu_idle(void)
694243830Sdim{
695243830Sdim	struct apm_softc *sc = &apm_softc;
696243830Sdim
697243830Sdim	if (sc->active) {
698221345Sdim
699221345Sdim		sc->bios.r.eax = (APM_BIOS <<8) | APM_CPUIDLE;
700221345Sdim		sc->bios.r.edx = sc->bios.r.ecx = sc->bios.r.ebx = 0;
701193326Sed		(void) apm_bioscall();
702221345Sdim	}
703234353Sdim	/*
704239462Sdim	 * Some APM implementation halts CPU in BIOS, whenever
705221345Sdim	 * "CPU-idle" function are invoked, but swtch() of
706226633Sdim	 * FreeBSD halts CPU, therefore, CPU is halted twice
707221345Sdim	 * in the sched loop. It makes the interrupt latency
708221345Sdim	 * terribly long and be able to cause a serious problem
709239462Sdim	 * in interrupt processing. We prevent it by removing
710221345Sdim	 * "hlt" operation from swtch() and managed it under
711221345Sdim	 * APM driver.
712221345Sdim	 */
713226633Sdim	if (!sc->active || sc->always_halt_cpu)
714226633Sdim		halt();	/* wait for interrupt */
715239462Sdim}
716226633Sdim
717226633Sdim/* inform APM BIOS that CPU is busy */
718221345Sdimvoid
719221345Sdimapm_cpu_busy(void)
720239462Sdim{
721221345Sdim	struct apm_softc *sc = &apm_softc;
722221345Sdim
723221345Sdim	/*
724226633Sdim	 * The APM specification says this is only necessary if your BIOS
725221345Sdim	 * slows down the processor in the idle task, otherwise it's not
726221345Sdim	 * necessary.
727221345Sdim	 */
728221345Sdim	if (sc->slow_idle_cpu && sc->active) {
729198092Srdivacky
730224145Sdim		sc->bios.r.eax = (APM_BIOS <<8) | APM_CPUBUSY;
731224145Sdim		sc->bios.r.edx = sc->bios.r.ecx = sc->bios.r.ebx = 0;
732224145Sdim		apm_bioscall();
733234353Sdim	}
734224145Sdim}
735224145Sdim
736221345Sdim
737221345Sdim/*
738221345Sdim * APM timeout routine:
739221345Sdim *
740198092Srdivacky * This routine is automatically called by timer once per second.
741224145Sdim */
742224145Sdim
743239462Sdimstatic void
744239462Sdimapm_timeout(void *dummy)
745239462Sdim{
746239462Sdim	struct apm_softc *sc = &apm_softc;
747221345Sdim
748221345Sdim	if (apm_op_inprog)
749239462Sdim		apm_lastreq_notify();
750239462Sdim
751239462Sdim	if (sc->standbys && sc->standby_countdown-- <= 0)
752239462Sdim		apm_do_standby();
753221345Sdim
754193326Sed	if (sc->suspends && sc->suspend_countdown-- <= 0)
755221345Sdim		apm_do_suspend();
756221345Sdim
757221345Sdim	if (!sc->bios_busy)
758221345Sdim		apm_processevent();
759221345Sdim
760226633Sdim	if (sc->active == 1)
761198092Srdivacky		/* Run slightly more oftan than 1 Hz */
762221345Sdim		apm_timeout_ch = timeout(apm_timeout, NULL, hz - 1);
763221345Sdim}
764239462Sdim
765239462Sdim/* enable APM BIOS */
766224145Sdimstatic void
767221345Sdimapm_event_enable(void)
768221345Sdim{
769221345Sdim	struct apm_softc *sc = &apm_softc;
770193326Sed
771221345Sdim	APM_DPRINT("called apm_event_enable()\n");
772221345Sdim	if (sc->initialized) {
773221345Sdim		sc->active = 1;
774226633Sdim		apm_timeout(sc);
775226633Sdim	}
776239462Sdim}
777224145Sdim
778224145Sdim/* disable APM BIOS */
779221345Sdimstatic void
780221345Sdimapm_event_disable(void)
781193326Sed{
782221345Sdim	struct apm_softc *sc = &apm_softc;
783
784	APM_DPRINT("called apm_event_disable()\n");
785	if (sc->initialized) {
786		untimeout(apm_timeout, NULL, apm_timeout_ch);
787		sc->active = 0;
788	}
789}
790
791/* halt CPU in scheduling loop */
792static void
793apm_halt_cpu(void)
794{
795	struct apm_softc *sc = &apm_softc;
796
797	if (sc->initialized)
798		sc->always_halt_cpu = 1;
799}
800
801/* don't halt CPU in scheduling loop */
802static void
803apm_not_halt_cpu(void)
804{
805	struct apm_softc *sc = &apm_softc;
806
807	if (sc->initialized)
808		sc->always_halt_cpu = 0;
809}
810
811/* device driver definitions */
812
813/*
814 * Module event
815 */
816
817static int
818apm_modevent(struct module *mod, int event, void *junk)
819{
820
821	switch (event) {
822	case MOD_LOAD:
823		if (!cold)
824			return (EPERM);
825		break;
826	case MOD_UNLOAD:
827		if (!cold && power_pm_get_type() == POWER_PM_TYPE_APM)
828			return (EBUSY);
829		break;
830	default:
831		break;
832	}
833
834	return (0);
835}
836
837/*
838 * Create "connection point"
839 */
840static void
841apm_identify(driver_t *driver, device_t parent)
842{
843	device_t child;
844
845	if (!cold) {
846		printf("Don't load this driver from userland!!\n");
847		return;
848	}
849
850	if (resource_disabled("apm", 0))
851		return;
852
853	child = BUS_ADD_CHILD(parent, 0, "apm", 0);
854	if (child == NULL)
855		panic("apm_identify");
856}
857
858/*
859 * probe for APM BIOS
860 */
861static int
862apm_probe(device_t dev)
863{
864#define APM_KERNBASE	KERNBASE
865	struct vm86frame	vmf;
866	struct apm_softc	*sc = &apm_softc;
867#ifdef PC98
868	int			rid;
869#endif
870
871	device_set_desc(dev, "APM BIOS");
872	if (device_get_unit(dev) > 0) {
873		printf("apm: Only one APM driver supported.\n");
874		return ENXIO;
875	}
876
877	if (power_pm_get_type() != POWER_PM_TYPE_NONE &&
878	    power_pm_get_type() != POWER_PM_TYPE_APM) {
879		printf("apm: Other PM system enabled.\n");
880		return ENXIO;
881	}
882
883	bzero(&vmf, sizeof(struct vm86frame));		/* safety */
884	bzero(&apm_softc, sizeof(apm_softc));
885	vmf.vmf_ah = APM_BIOS;
886	vmf.vmf_al = APM_INSTCHECK;
887	vmf.vmf_bx = 0;
888	if (vm86_intcall(APM_INT, &vmf))
889		return ENXIO;			/* APM not found */
890	if (vmf.vmf_bx != 0x504d) {
891		printf("apm: incorrect signature (0x%x)\n", vmf.vmf_bx);
892		return ENXIO;
893	}
894	if ((vmf.vmf_cx & (APM_32BIT_SUPPORT | APM_16BIT_SUPPORT)) == 0) {
895		printf("apm: protected mode connections are not supported\n");
896		return ENXIO;
897	}
898
899	apm_version = vmf.vmf_ax;
900	sc->slow_idle_cpu = ((vmf.vmf_cx & APM_CPUIDLE_SLOW) != 0);
901	sc->disabled = ((vmf.vmf_cx & APM_DISABLED) != 0);
902	sc->disengaged = ((vmf.vmf_cx & APM_DISENGAGED) != 0);
903
904	vmf.vmf_ah = APM_BIOS;
905	vmf.vmf_al = APM_DISCONNECT;
906	vmf.vmf_bx = 0;
907        vm86_intcall(APM_INT, &vmf);		/* disconnect, just in case */
908
909#ifdef PC98
910	/* PC98 have bogos APM 32bit BIOS */
911	if ((vmf.vmf_cx & APM_32BIT_SUPPORT) == 0)
912		return ENXIO;
913	rid = 0;
914	bus_set_resource(dev, SYS_RES_IOPORT, rid,
915			 APM_NECSMM_PORT, APM_NECSMM_PORTSZ);
916	sc->sc_res = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid,
917			 APM_NECSMM_PORT, ~0, APM_NECSMM_PORTSZ, RF_ACTIVE);
918	if (sc->sc_res == NULL) {
919		printf("apm: cannot open NEC smm device\n");
920		return ENXIO;
921	}
922	bus_release_resource(dev, SYS_RES_IOPORT, rid, sc->sc_res);
923
924	vmf.vmf_ah = APM_BIOS;
925	vmf.vmf_al = APM_PROT32CONNECT;
926	vmf.vmf_bx = 0;
927	if (vm86_intcall(APM_INT, &vmf)) {
928		printf("apm: 32-bit connection error.\n");
929		return (ENXIO);
930 	}
931
932	sc->bios.seg.code32.base = (vmf.vmf_ax << 4) + APM_KERNBASE;
933	sc->bios.seg.code32.limit = 0xffff;
934	sc->bios.seg.code16.base = (vmf.vmf_cx << 4) + APM_KERNBASE;
935	sc->bios.seg.code16.limit = 0xffff;
936	sc->bios.seg.data.base = (vmf.vmf_dx << 4) + APM_KERNBASE;
937	sc->bios.seg.data.limit = 0xffff;
938	sc->bios.entry = vmf.vmf_ebx;
939	sc->connectmode = APM_PROT32CONNECT;
940#else
941	if ((vmf.vmf_cx & APM_32BIT_SUPPORT) != 0) {
942		vmf.vmf_ah = APM_BIOS;
943		vmf.vmf_al = APM_PROT32CONNECT;
944		vmf.vmf_bx = 0;
945		if (vm86_intcall(APM_INT, &vmf)) {
946			printf("apm: 32-bit connection error.\n");
947			return (ENXIO);
948 		}
949		sc->bios.seg.code32.base = (vmf.vmf_ax << 4) + APM_KERNBASE;
950		sc->bios.seg.code32.limit = 0xffff;
951		sc->bios.seg.code16.base = (vmf.vmf_cx << 4) + APM_KERNBASE;
952		sc->bios.seg.code16.limit = 0xffff;
953		sc->bios.seg.data.base = (vmf.vmf_dx << 4) + APM_KERNBASE;
954		sc->bios.seg.data.limit = 0xffff;
955		sc->bios.entry = vmf.vmf_ebx;
956		sc->connectmode = APM_PROT32CONNECT;
957 	} else {
958		/* use 16-bit connection */
959		vmf.vmf_ah = APM_BIOS;
960		vmf.vmf_al = APM_PROT16CONNECT;
961		vmf.vmf_bx = 0;
962		if (vm86_intcall(APM_INT, &vmf)) {
963			printf("apm: 16-bit connection error.\n");
964			return (ENXIO);
965		}
966		sc->bios.seg.code16.base = (vmf.vmf_ax << 4) + APM_KERNBASE;
967		sc->bios.seg.code16.limit = 0xffff;
968		sc->bios.seg.data.base = (vmf.vmf_cx << 4) + APM_KERNBASE;
969		sc->bios.seg.data.limit = 0xffff;
970		sc->bios.entry = vmf.vmf_bx;
971		sc->connectmode = APM_PROT16CONNECT;
972	}
973#endif
974	return(0);
975}
976
977
978/*
979 * return 0 if the user will notice and handle the event,
980 * return 1 if the kernel driver should do so.
981 */
982static int
983apm_record_event(struct apm_softc *sc, u_int event_type)
984{
985	struct apm_event_info *evp;
986
987	if ((sc->sc_flags & SCFLAG_OPEN) == 0)
988		return 1;		/* no user waiting */
989	if (sc->event_count == APM_NEVENTS)
990		return 1;			/* overflow */
991	if (sc->event_filter[event_type] == 0)
992		return 1;		/* not registered */
993	evp = &sc->event_list[sc->event_ptr];
994	sc->event_count++;
995	sc->event_ptr++;
996	sc->event_ptr %= APM_NEVENTS;
997	evp->type = event_type;
998	evp->index = ++apm_evindex;
999	selwakeuppri(&sc->sc_rsel, PZERO);
1000	return (sc->sc_flags & SCFLAG_OCTL) ? 0 : 1; /* user may handle */
1001}
1002
1003/* Power profile */
1004static void
1005apm_power_profile(struct apm_softc *sc)
1006{
1007	int state;
1008	struct apm_info info;
1009	static int apm_acline = 0;
1010
1011	if (apm_get_info(&info))
1012		return;
1013
1014	if (apm_acline != info.ai_acline) {
1015		apm_acline = info.ai_acline;
1016		state = apm_acline ? POWER_PROFILE_PERFORMANCE : POWER_PROFILE_ECONOMY;
1017		power_profile_set_state(state);
1018	}
1019}
1020
1021/* Process APM event */
1022static void
1023apm_processevent(void)
1024{
1025	int apm_event;
1026	struct apm_softc *sc = &apm_softc;
1027
1028#define OPMEV_DEBUGMESSAGE(symbol) case symbol:				\
1029	APM_DPRINT("Received APM Event: " #symbol "\n");
1030
1031	do {
1032		apm_event = apm_getevent();
1033		switch (apm_event) {
1034		    OPMEV_DEBUGMESSAGE(PMEV_STANDBYREQ);
1035			if (apm_op_inprog == 0) {
1036			    apm_op_inprog++;
1037			    if (apm_record_event(sc, apm_event)) {
1038				apm_suspend(PMST_STANDBY);
1039			    }
1040			}
1041			break;
1042		    OPMEV_DEBUGMESSAGE(PMEV_USERSTANDBYREQ);
1043			if (apm_op_inprog == 0) {
1044			    apm_op_inprog++;
1045			    if (apm_record_event(sc, apm_event)) {
1046				apm_suspend(PMST_STANDBY);
1047			    }
1048			}
1049			break;
1050		    OPMEV_DEBUGMESSAGE(PMEV_SUSPENDREQ);
1051 			apm_lastreq_notify();
1052			if (apm_op_inprog == 0) {
1053			    apm_op_inprog++;
1054			    if (apm_record_event(sc, apm_event)) {
1055				apm_do_suspend();
1056			    }
1057			}
1058			return; /* XXX skip the rest */
1059		    OPMEV_DEBUGMESSAGE(PMEV_USERSUSPENDREQ);
1060 			apm_lastreq_notify();
1061			if (apm_op_inprog == 0) {
1062			    apm_op_inprog++;
1063			    if (apm_record_event(sc, apm_event)) {
1064				apm_do_suspend();
1065			    }
1066			}
1067			return; /* XXX skip the rest */
1068		    OPMEV_DEBUGMESSAGE(PMEV_CRITSUSPEND);
1069			apm_do_suspend();
1070			break;
1071		    OPMEV_DEBUGMESSAGE(PMEV_NORMRESUME);
1072			apm_record_event(sc, apm_event);
1073			apm_resume();
1074			break;
1075		    OPMEV_DEBUGMESSAGE(PMEV_CRITRESUME);
1076			apm_record_event(sc, apm_event);
1077			apm_resume();
1078			break;
1079		    OPMEV_DEBUGMESSAGE(PMEV_STANDBYRESUME);
1080			apm_record_event(sc, apm_event);
1081			break;
1082		    OPMEV_DEBUGMESSAGE(PMEV_BATTERYLOW);
1083			if (apm_record_event(sc, apm_event)) {
1084			    apm_battery_low();
1085			    apm_suspend(PMST_SUSPEND);
1086			}
1087			break;
1088		    OPMEV_DEBUGMESSAGE(PMEV_POWERSTATECHANGE);
1089			apm_record_event(sc, apm_event);
1090			apm_power_profile(sc);
1091			break;
1092		    OPMEV_DEBUGMESSAGE(PMEV_UPDATETIME);
1093			apm_record_event(sc, apm_event);
1094			inittodr(0);	/* adjust time to RTC */
1095			break;
1096		    OPMEV_DEBUGMESSAGE(PMEV_CAPABILITIESCHANGE);
1097			apm_record_event(sc, apm_event);
1098			apm_power_profile(sc);
1099			break;
1100		    case PMEV_NOEVENT:
1101			break;
1102		    default:
1103			printf("Unknown Original APM Event 0x%x\n", apm_event);
1104			    break;
1105		}
1106	} while (apm_event != PMEV_NOEVENT);
1107#ifdef PC98
1108	apm_disable_smm(sc);
1109#endif
1110}
1111
1112/*
1113 * Attach APM:
1114 *
1115 * Initialize APM driver
1116 */
1117
1118static int
1119apm_attach(device_t dev)
1120{
1121	struct apm_softc	*sc = &apm_softc;
1122	int			drv_version;
1123#ifdef PC98
1124	int			rid;
1125#endif
1126
1127	if (device_get_flags(dev) & 0x20)
1128		statclock_disable = 1;
1129
1130	sc->initialized = 0;
1131
1132	/* Must be externally enabled */
1133	sc->active = 0;
1134
1135	/* Always call HLT in idle loop */
1136	sc->always_halt_cpu = 1;
1137
1138	getenv_int("debug.apm_debug", &apm_debug);
1139
1140	/* print bootstrap messages */
1141	APM_DPRINT("apm: APM BIOS version %04lx\n",  apm_version);
1142	APM_DPRINT("apm: Code16 0x%08x, Data 0x%08x\n",
1143	    sc->bios.seg.code16.base, sc->bios.seg.data.base);
1144	APM_DPRINT("apm: Code entry 0x%08x, Idling CPU %s, Management %s\n",
1145	    sc->bios.entry, is_enabled(sc->slow_idle_cpu),
1146	    is_enabled(!sc->disabled));
1147	APM_DPRINT("apm: CS_limit=0x%x, DS_limit=0x%x\n",
1148	    sc->bios.seg.code16.limit, sc->bios.seg.data.limit);
1149
1150#ifdef PC98
1151	rid = 0;
1152	sc->sc_res = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid,
1153			 APM_NECSMM_PORT, ~0, APM_NECSMM_PORTSZ, RF_ACTIVE);
1154	if (sc->sc_res == NULL)
1155		panic("%s: counldn't map I/O ports", device_get_name(dev));
1156	sc->sc_iot = rman_get_bustag(sc->sc_res);
1157	sc->sc_ioh = rman_get_bushandle(sc->sc_res);
1158
1159	if (apm_version==0x112 || apm_version==0x111 || apm_version==0x110)
1160		apm_necsmm_addr = APM_NECSMM_PORT;
1161	else
1162		apm_necsmm_addr = 0;
1163	apm_necsmm_mask = ~APM_NECSMM_EN;
1164#endif /* PC98 */
1165
1166	/*
1167         * In one test, apm bios version was 1.02; an attempt to register
1168         * a 1.04 driver resulted in a 1.00 connection!  Registering a
1169         * 1.02 driver resulted in a 1.02 connection.
1170         */
1171	drv_version = apm_version > 0x102 ? 0x102 : apm_version;
1172	for (; drv_version > 0x100; drv_version--)
1173		if (apm_driver_version(drv_version) == 0)
1174			break;
1175	sc->minorversion = ((drv_version & 0x00f0) >>  4) * 10 +
1176		((drv_version & 0x000f) >> 0);
1177	sc->majorversion = ((drv_version & 0xf000) >> 12) * 10 +
1178		((apm_version & 0x0f00) >> 8);
1179
1180	sc->intversion = INTVERSION(sc->majorversion, sc->minorversion);
1181
1182	if (sc->intversion >= INTVERSION(1, 1))
1183		APM_DPRINT("apm: Engaged control %s\n", is_enabled(!sc->disengaged));
1184	device_printf(dev, "found APM BIOS v%ld.%ld, connected at v%d.%d\n",
1185	       ((apm_version & 0xf000) >> 12) * 10 + ((apm_version & 0x0f00) >> 8),
1186	       ((apm_version & 0x00f0) >> 4) * 10 + ((apm_version & 0x000f) >> 0),
1187	       sc->majorversion, sc->minorversion);
1188
1189
1190	APM_DPRINT("apm: Slow Idling CPU %s\n", is_enabled(sc->slow_idle_cpu));
1191	/* enable power management */
1192	if (sc->disabled) {
1193		if (apm_enable_disable_pm(1)) {
1194			APM_DPRINT("apm: *Warning* enable function failed! [%x]\n",
1195			    (sc->bios.r.eax >> 8) & 0xff);
1196		}
1197	}
1198
1199	/* engage power managment (APM 1.1 or later) */
1200	if (sc->intversion >= INTVERSION(1, 1) && sc->disengaged) {
1201		if (apm_engage_disengage_pm(1)) {
1202			APM_DPRINT("apm: *Warning* engage function failed err=[%x]",
1203			    (sc->bios.r.eax >> 8) & 0xff);
1204			APM_DPRINT(" (Docked or using external power?).\n");
1205		}
1206	}
1207
1208	/* Power the system off using APM */
1209	EVENTHANDLER_REGISTER(shutdown_final, apm_power_off, NULL,
1210			      SHUTDOWN_PRI_LAST);
1211
1212	/* Register APM again to pass the correct argument of pm_func. */
1213	power_pm_register(POWER_PM_TYPE_APM, apm_pm_func, sc);
1214
1215	sc->initialized = 1;
1216	sc->suspending = 0;
1217
1218	make_dev(&apm_cdevsw, 0, 0, 5, 0664, "apm");
1219	make_dev(&apm_cdevsw, 8, 0, 5, 0660, "apmctl");
1220	return 0;
1221}
1222
1223static int
1224apmopen(struct cdev *dev, int flag, int fmt, struct thread *td)
1225{
1226	struct apm_softc *sc = &apm_softc;
1227	int ctl = APMDEV(dev);
1228
1229	if (!sc->initialized)
1230		return (ENXIO);
1231
1232	switch (ctl) {
1233	case APMDEV_CTL:
1234		if (!(flag & FWRITE))
1235			return EINVAL;
1236		if (sc->sc_flags & SCFLAG_OCTL)
1237			return EBUSY;
1238		sc->sc_flags |= SCFLAG_OCTL;
1239		bzero(sc->event_filter, sizeof sc->event_filter);
1240		break;
1241	case APMDEV_NORMAL:
1242		sc->sc_flags |= SCFLAG_ONORMAL;
1243		break;
1244	default:
1245		return ENXIO;
1246		break;
1247	}
1248	return 0;
1249}
1250
1251static int
1252apmclose(struct cdev *dev, int flag, int fmt, struct thread *td)
1253{
1254	struct apm_softc *sc = &apm_softc;
1255	int ctl = APMDEV(dev);
1256
1257	switch (ctl) {
1258	case APMDEV_CTL:
1259		apm_lastreq_rejected();
1260		sc->sc_flags &= ~SCFLAG_OCTL;
1261		bzero(sc->event_filter, sizeof sc->event_filter);
1262		break;
1263	case APMDEV_NORMAL:
1264		sc->sc_flags &= ~SCFLAG_ONORMAL;
1265		break;
1266	}
1267	if ((sc->sc_flags & SCFLAG_OPEN) == 0) {
1268		sc->event_count = 0;
1269		sc->event_ptr = 0;
1270	}
1271	return 0;
1272}
1273
1274static int
1275apmioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, struct thread *td)
1276{
1277	struct apm_softc *sc = &apm_softc;
1278	struct apm_bios_arg *args;
1279	int error = 0;
1280	int ret;
1281	int newstate;
1282
1283	if (!sc->initialized)
1284		return (ENXIO);
1285	APM_DPRINT("APM ioctl: cmd = 0x%lx\n", cmd);
1286	switch (cmd) {
1287	case APMIO_SUSPEND:
1288		if (!(flag & FWRITE))
1289			return (EPERM);
1290		if (sc->active)
1291			apm_suspend(PMST_SUSPEND);
1292		else
1293			error = EINVAL;
1294		break;
1295
1296	case APMIO_STANDBY:
1297		if (!(flag & FWRITE))
1298			return (EPERM);
1299		if (sc->active)
1300			apm_suspend(PMST_STANDBY);
1301		else
1302			error = EINVAL;
1303		break;
1304
1305	case APMIO_GETINFO_OLD:
1306		{
1307			struct apm_info info;
1308			apm_info_old_t aiop;
1309
1310			if (apm_get_info(&info))
1311				error = ENXIO;
1312			aiop = (apm_info_old_t)addr;
1313			aiop->ai_major = info.ai_major;
1314			aiop->ai_minor = info.ai_minor;
1315			aiop->ai_acline = info.ai_acline;
1316			aiop->ai_batt_stat = info.ai_batt_stat;
1317			aiop->ai_batt_life = info.ai_batt_life;
1318			aiop->ai_status = info.ai_status;
1319		}
1320		break;
1321	case APMIO_GETINFO:
1322		if (apm_get_info((apm_info_t)addr))
1323			error = ENXIO;
1324		break;
1325	case APMIO_GETPWSTATUS:
1326		if (apm_get_pwstatus((apm_pwstatus_t)addr))
1327			error = ENXIO;
1328		break;
1329	case APMIO_ENABLE:
1330		if (!(flag & FWRITE))
1331			return (EPERM);
1332		apm_event_enable();
1333		break;
1334	case APMIO_DISABLE:
1335		if (!(flag & FWRITE))
1336			return (EPERM);
1337		apm_event_disable();
1338		break;
1339	case APMIO_HALTCPU:
1340		if (!(flag & FWRITE))
1341			return (EPERM);
1342		apm_halt_cpu();
1343		break;
1344	case APMIO_NOTHALTCPU:
1345		if (!(flag & FWRITE))
1346			return (EPERM);
1347		apm_not_halt_cpu();
1348		break;
1349	case APMIO_DISPLAY:
1350		if (!(flag & FWRITE))
1351			return (EPERM);
1352		newstate = *(int *)addr;
1353		if (apm_display(newstate))
1354			error = ENXIO;
1355		break;
1356	case APMIO_BIOS:
1357		if (!(flag & FWRITE))
1358			return (EPERM);
1359		/* XXX compatibility with the old interface */
1360		args = (struct apm_bios_arg *)addr;
1361		sc->bios.r.eax = args->eax;
1362		sc->bios.r.ebx = args->ebx;
1363		sc->bios.r.ecx = args->ecx;
1364		sc->bios.r.edx = args->edx;
1365		sc->bios.r.esi = args->esi;
1366		sc->bios.r.edi = args->edi;
1367		if ((ret = apm_bioscall())) {
1368			/*
1369			 * Return code 1 means bios call was unsuccessful.
1370			 * Error code is stored in %ah.
1371			 * Return code -1 means bios call was unsupported
1372			 * in the APM BIOS version.
1373			 */
1374			if (ret == -1) {
1375				error = EINVAL;
1376			}
1377		} else {
1378			/*
1379			 * Return code 0 means bios call was successful.
1380			 * We need only %al and can discard %ah.
1381			 */
1382			sc->bios.r.eax &= 0xff;
1383		}
1384		args->eax = sc->bios.r.eax;
1385		args->ebx = sc->bios.r.ebx;
1386		args->ecx = sc->bios.r.ecx;
1387		args->edx = sc->bios.r.edx;
1388		args->esi = sc->bios.r.esi;
1389		args->edi = sc->bios.r.edi;
1390		break;
1391	default:
1392		error = EINVAL;
1393		break;
1394	}
1395
1396	/* for /dev/apmctl */
1397	if (APMDEV(dev) == APMDEV_CTL) {
1398		struct apm_event_info *evp;
1399		int i;
1400
1401		error = 0;
1402		switch (cmd) {
1403		case APMIO_NEXTEVENT:
1404			if (!sc->event_count) {
1405				error = EAGAIN;
1406			} else {
1407				evp = (struct apm_event_info *)addr;
1408				i = sc->event_ptr + APM_NEVENTS - sc->event_count;
1409				i %= APM_NEVENTS;
1410				*evp = sc->event_list[i];
1411				sc->event_count--;
1412			}
1413			break;
1414		case APMIO_REJECTLASTREQ:
1415			if (apm_lastreq_rejected()) {
1416				error = EINVAL;
1417			}
1418			break;
1419		default:
1420			error = EINVAL;
1421			break;
1422		}
1423	}
1424
1425	return error;
1426}
1427
1428static int
1429apmwrite(struct cdev *dev, struct uio *uio, int ioflag)
1430{
1431	struct apm_softc *sc = &apm_softc;
1432	u_int event_type;
1433	int error;
1434	u_char enabled;
1435
1436	if (APMDEV(dev) != APMDEV_CTL)
1437		return(ENODEV);
1438	if (uio->uio_resid != sizeof(u_int))
1439		return(E2BIG);
1440
1441	if ((error = uiomove((caddr_t)&event_type, sizeof(u_int), uio)))
1442		return(error);
1443
1444	if (event_type < 0 || event_type >= APM_NPMEV)
1445		return(EINVAL);
1446
1447	if (sc->event_filter[event_type] == 0) {
1448		enabled = 1;
1449	} else {
1450		enabled = 0;
1451	}
1452	sc->event_filter[event_type] = enabled;
1453	APM_DPRINT("apmwrite: event 0x%x %s\n", event_type, is_enabled(enabled));
1454
1455	return uio->uio_resid;
1456}
1457
1458static int
1459apmpoll(struct cdev *dev, int events, struct thread *td)
1460{
1461	struct apm_softc *sc = &apm_softc;
1462	int revents = 0;
1463
1464	if (events & (POLLIN | POLLRDNORM)) {
1465		if (sc->event_count) {
1466			revents |= events & (POLLIN | POLLRDNORM);
1467		} else {
1468			selrecord(td, &sc->sc_rsel);
1469		}
1470	}
1471
1472	return (revents);
1473}
1474
1475static device_method_t apm_methods[] = {
1476	/* Device interface */
1477	DEVMETHOD(device_identify,	apm_identify),
1478	DEVMETHOD(device_probe,		apm_probe),
1479	DEVMETHOD(device_attach,	apm_attach),
1480
1481	{ 0, 0 }
1482};
1483
1484static driver_t apm_driver = {
1485	"apm",
1486	apm_methods,
1487	1,			/* no softc (XXX) */
1488};
1489
1490static devclass_t apm_devclass;
1491
1492DRIVER_MODULE(apm, legacy, apm_driver, apm_devclass, apm_modevent, 0);
1493MODULE_VERSION(apm, 1);
1494
1495static int
1496apm_pm_func(u_long cmd, void *arg, ...)
1497{
1498	int	state, apm_state;
1499	int	error;
1500	va_list	ap;
1501
1502	error = 0;
1503	switch (cmd) {
1504	case POWER_CMD_SUSPEND:
1505		va_start(ap, arg);
1506		state = va_arg(ap, int);
1507		va_end(ap);
1508
1509		switch (state) {
1510		case POWER_SLEEP_STATE_STANDBY:
1511			apm_state = PMST_STANDBY;
1512			break;
1513		case POWER_SLEEP_STATE_SUSPEND:
1514		case POWER_SLEEP_STATE_HIBERNATE:
1515			apm_state = PMST_SUSPEND;
1516			break;
1517		default:
1518			error = EINVAL;
1519			goto out;
1520		}
1521
1522		apm_suspend(apm_state);
1523		break;
1524
1525	default:
1526		error = EINVAL;
1527		goto out;
1528	}
1529
1530out:
1531	return (error);
1532}
1533
1534static void
1535apm_pm_register(void *arg)
1536{
1537
1538	if (!resource_disabled("apm", 0))
1539		power_pm_register(POWER_PM_TYPE_APM, apm_pm_func, NULL);
1540}
1541
1542SYSINIT(power, SI_SUB_KLD, SI_ORDER_ANY, apm_pm_register, 0);
1543