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