1 /**	$MirOS: src/sys/arch/i386/i386/apm.c,v 1.6 2005/07/01 19:11:18 tg Exp $ */
2 /*	$OpenBSD: apm.c,v 1.66 2005/06/16 16:49:04 beck Exp $	*/
3 
4 /*-
5  * Copyright (c) 1998-2001 Michael Shalayeff. All rights reserved.
6  * Copyright (c) 1995 John T. Kohl.  All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. All advertising materials mentioning features or use of this software
17  *    must display the following acknowledgement:
18  *	This product includes software developed by the University of
19  *	California, Berkeley and its contributors.
20  * 4. Neither the name of the University nor the names of its contributors
21  *    may be used to endorse or promote products derived from this software
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30  * OR SERVICES; LOSS OF MIND, USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  *
36  */
37 
38 #include "apm.h"
39 
40 #if NAPM > 1
41 #error only one APM device may be configured
42 #endif
43 
44 #include <sys/param.h>
45 #include <sys/systm.h>
46 #include <sys/signalvar.h>
47 #include <sys/kernel.h>
48 #include <sys/kthread.h>
49 #include <sys/lock.h>
50 #include <sys/proc.h>
51 #include <sys/user.h>
52 #include <sys/malloc.h>
53 #include <sys/device.h>
54 #include <sys/fcntl.h>
55 #include <sys/ioctl.h>
56 #include <sys/event.h>
57 #include <sys/mount.h>	/* for vfs_syncwait() proto */
58 #include <sys/dkstat.h>
59 
60 #include <machine/conf.h>
61 #include <machine/cpu.h>
62 #include <machine/cpufunc.h>
63 #include <machine/gdt.h>
64 #include <machine/psl.h>
65 
66 #include <dev/isa/isareg.h>
67 #include <i386/isa/isa_machdep.h>
68 #include <i386/isa/nvram.h>
69 #include <dev/isa/isavar.h>
70 
71 #include <machine/biosvar.h>
72 #include <machine/apmvar.h>
73 
74 #if defined(APMDEBUG)
75 #define DPRINTF(x)	printf x
76 #else
77 #define	DPRINTF(x)	/**/
78 #endif
79 
80 #define	APM_LOCK(sc)	lockmgr(&(sc)->sc_lock, LK_EXCLUSIVE, NULL, curproc)
81 #define	APM_UNLOCK(sc)	lockmgr(&(sc)->sc_lock, LK_RELEASE, NULL, curproc)
82 
83 struct cfdriver apm_cd = {
84 	NULL, "apm", DV_DULL
85 };
86 
87 struct apm_softc {
88 	struct device sc_dev;
89 	struct klist sc_note;
90 	int	sc_flags;
91 	int	batt_life;
92 	struct proc *sc_thread;
93 	struct lock sc_lock;
94 };
95 #define	SCFLAG_OREAD	0x0000001
96 #define	SCFLAG_OWRITE	0x0000002
97 #define	SCFLAG_OPEN	(SCFLAG_OREAD|SCFLAG_OWRITE)
98 
99 int apmprobe(struct device *, void *, void *);
100 void apmattach(struct device *, struct device *, void *);
101 
102 struct cfattach apm_ca = {
103 	sizeof(struct apm_softc), apmprobe, apmattach
104 };
105 
106 void filt_apmrdetach(struct knote *kn);
107 int filt_apmread(struct knote *kn, long hint);
108 
109 struct filterops apmread_filtops =
110 	{ 1, NULL, filt_apmrdetach, filt_apmread};
111 
112 /* battery percentage at where we get verbose in our warnings.  This
113    value can be changed using sysctl(8), value machdep.apmwarn.
114    Setting it to zero kills all warnings */
115 int cpu_apmwarn = 10;
116 
117 #define	APM_RESUME_HOLDOFF	3
118 
119 /*
120  * Flags to control kernel display
121  *	SCFLAG_NOPRINT:		do not output APM power messages due to
122  *				a power change event.
123  *
124  *	SCFLAG_PCTPRINT:	do not output APM power messages due to
125  *				to a power change event unless the battery
126  *				percentage changes.
127  */
128 #define SCFLAG_NOPRINT	0x0008000
129 #define SCFLAG_PCTPRINT	0x0004000
130 #define SCFLAG_PRINT	(SCFLAG_NOPRINT|SCFLAG_PCTPRINT)
131 
132 #define	APMUNIT(dev)	(minor(dev)&0xf0)
133 #define	APMDEV(dev)	(minor(dev)&0x0f)
134 #define APMDEV_NORMAL	0
135 #define APMDEV_CTL	8
136 
137 int apm_standbys;
138 int apm_userstandbys;
139 int apm_suspends;
140 int apm_resumes;
141 int apm_battlow;
142 int apm_evindex;
143 int apm_error;
144 int apm_op_inprog;
145 
146 u_int apm_flags;
147 u_char apm_majver;
148 u_char apm_minver;
149 int apm_dobusy = 0;
150 int apm_doidle = 0;
151 int apm_bebatt = 0;
152 int apm_idle_called = 0;
153 
154 struct {
155 	u_int32_t entry;
156 	u_int16_t seg;
157 	u_int16_t pad;
158 } apm_ep;
159 
160 struct apmregs {
161 	u_int32_t	ax;
162 	u_int32_t	bx;
163 	u_int32_t	cx;
164 	u_int32_t	dx;
165 };
166 
167 int  apmcall(u_int, u_int, struct apmregs *);
168 void apm_power_print(struct apm_softc *, struct apmregs *);
169 int  apm_handle_event(struct apm_softc *, struct apmregs *);
170 void apm_set_ver(struct apm_softc *);
171 int  apm_periodic_check(struct apm_softc *);
172 void apm_thread_create(void *v);
173 void apm_thread(void *);
174 void apm_disconnect(struct apm_softc *);
175 void apm_perror(const char *, struct apmregs *);
176 void apm_powmgt_enable(int onoff);
177 void apm_powmgt_engage(int onoff, u_int devid);
178 /* void apm_devpowmgt_enable(int onoff, u_int devid); */
179 int  apm_record_event(struct apm_softc *sc, u_int type);
180 const char *apm_err_translate(int code);
181 
182 #define	apm_get_powstat(r) apmcall(APM_POWER_STATUS, APM_DEV_ALLDEVS, r)
183 void	apm_standby(void);
184 void	apm_suspend(void);
185 void	apm_resume(struct apm_softc *, struct apmregs *);
186 
187 static int __inline
apm_get_event(struct apmregs * r)188 apm_get_event(struct apmregs *r)
189 {
190 	int rv;
191 	bzero(r, sizeof(*r));
192 	rv = apmcall(APM_GET_PM_EVENT, 0, r);
193 	return rv;
194 }
195 
196 const char *
apm_err_translate(code)197 apm_err_translate(code)
198 	int code;
199 {
200 	switch(code) {
201 	case APM_ERR_PM_DISABLED:
202 		return "power management disabled";
203 	case APM_ERR_REALALREADY:
204 		return "real mode interface already connected";
205 	case APM_ERR_NOTCONN:
206 		return "interface not connected";
207 	case APM_ERR_16ALREADY:
208 		return "16-bit interface already connected";
209 	case APM_ERR_16NOTSUPP:
210 		return "16-bit interface not supported";
211 	case APM_ERR_32ALREADY:
212 		return "32-bit interface already connected";
213 	case APM_ERR_32NOTSUPP:
214 		return "32-bit interface not supported";
215 	case APM_ERR_UNRECOG_DEV:
216 		return "unrecognized device ID";
217 	case APM_ERR_ERANGE:
218 		return "parameter out of range";
219 	case APM_ERR_NOTENGAGED:
220 		return "interface not engaged";
221 	case APM_ERR_UNABLE:
222 		return "unable to enter requested state";
223 	case APM_ERR_NOEVENTS:
224 		return "No pending events";
225 	case APM_ERR_NOT_PRESENT:
226 		return "No APM present";
227 	default:
228 		return "unknown error code?";
229 	}
230 }
231 
232 int apmerrors = 0;
233 
234 void
apm_perror(str,regs)235 apm_perror(str, regs)
236 	const char *str;
237 	struct apmregs *regs;
238 {
239 	printf("apm0: APM %s: %s (%d)\n", str,
240 	    apm_err_translate(APM_ERR_CODE(regs)),
241 	    APM_ERR_CODE(regs));
242 	delay(1000000);
243 
244 	apmerrors++;
245 }
246 
247 void
apm_power_print(sc,regs)248 apm_power_print (sc, regs)
249 	struct apm_softc *sc;
250 	struct apmregs *regs;
251 {
252 #if !defined(APM_NOPRINT)
253 	sc->batt_life = BATT_LIFE(regs);
254 	if (BATT_LIFE(regs) != APM_BATT_LIFE_UNKNOWN) {
255 		printf("%s: battery life expectancy %d%%\n",
256 		    sc->sc_dev.dv_xname,
257 		    BATT_LIFE(regs));
258 	}
259 	printf("%s: AC ", sc->sc_dev.dv_xname);
260 	switch (AC_STATE(regs)) {
261 	case APM_AC_OFF:
262 		printf("off,");
263 		break;
264 	case APM_AC_ON:
265 		printf("on,");
266 		break;
267 	case APM_AC_BACKUP:
268 		printf("backup power,");
269 		break;
270 	default:
271 	case APM_AC_UNKNOWN:
272 		printf("unknown,");
273 		break;
274 	}
275 	if (apm_minver == 0) {
276 		printf(" battery is ");
277 		switch (BATT_STATE(regs)) {
278 		case APM_BATT_HIGH:
279 			printf("high");
280 			break;
281 		case APM_BATT_LOW:
282 			printf("low");
283 			break;
284 		case APM_BATT_CRITICAL:
285 			printf("CRITICAL");
286 			break;
287 		case APM_BATT_CHARGING:
288 			printf("charging");
289 			break;
290 		case APM_BATT_UNKNOWN:
291 			printf("unknown");
292 			break;
293 		default:
294 			printf("undecoded (%x)", BATT_STATE(regs));
295 			break;
296 		}
297 	} else if (apm_minver >= 1) {
298 		if (BATT_FLAGS(regs) & APM_BATT_FLAG_NOBATTERY)
299 			printf(" no battery");
300 		else {
301 			printf(" battery charge ");
302 			if (BATT_FLAGS(regs) & APM_BATT_FLAG_HIGH)
303 				printf("high");
304 			else if (BATT_FLAGS(regs) & APM_BATT_FLAG_LOW)
305 				printf("low");
306 			else if (BATT_FLAGS(regs) & APM_BATT_FLAG_CRITICAL)
307 				printf("critical");
308 			else
309 				printf("unknown");
310 			if (BATT_FLAGS(regs) & APM_BATT_FLAG_CHARGING)
311 				printf(", charging");
312 			if (BATT_REM_VALID(regs)) {
313 				int life = BATT_REMAINING(regs);
314 				if (apm_bebatt)
315 					life = swap16(life);
316 				printf(", estimated %d:%02d hours",
317 				    life / 60, life % 60);
318 			}
319 		}
320 	}
321 
322 	printf("\n");
323 #endif
324 }
325 
326 void
apm_suspend()327 apm_suspend()
328 {
329 	dopowerhooks(PWR_SUSPEND);
330 
331 	if (cold)
332 		vfs_syncwait(0);
333 
334 	(void)apm_set_powstate(APM_DEV_ALLDEVS, APM_SYS_SUSPEND);
335 }
336 
337 void
apm_standby()338 apm_standby()
339 {
340 	dopowerhooks(PWR_STANDBY);
341 
342 	if (cold)
343 		vfs_syncwait(0);
344 
345 	(void)apm_set_powstate(APM_DEV_ALLDEVS, APM_SYS_STANDBY);
346 }
347 
348 void
apm_resume(sc,regs)349 apm_resume(sc, regs)
350 	struct apm_softc *sc;
351 	struct apmregs *regs;
352 {
353 	extern int perflevel;
354 
355 	apm_resumes = APM_RESUME_HOLDOFF;
356 
357 	/* they say that some machines may require reinitializing the clock */
358 	initrtclock();
359 
360 	inittodr(time.tv_sec);
361 	/* lower bit in cx means pccard was powered down */
362 	dopowerhooks(PWR_RESUME);
363 	apm_record_event(sc, regs->bx);
364 
365 	/* acknowledge any rtc interrupt we may have missed */
366 	rtcdrain(NULL);
367 
368 	/* restore hw.setperf */
369 	if (cpu_setperf != NULL)
370 		cpu_setperf(perflevel);
371 }
372 
373 int
apm_record_event(sc,type)374 apm_record_event(sc, type)
375 	struct apm_softc *sc;
376 	u_int type;
377 {
378 	if (!apm_error && (sc->sc_flags & SCFLAG_OPEN) == 0) {
379 		DPRINTF(("apm_record_event: no user waiting\n"));
380 		apm_error++;
381 		return 1;
382 	}
383 
384 	apm_evindex++;
385 	KNOTE(&sc->sc_note, APM_EVENT_COMPOSE(type, apm_evindex));
386 
387 	return (0);
388 }
389 
390 int
apm_handle_event(sc,regs)391 apm_handle_event(sc, regs)
392 	struct apm_softc *sc;
393 	struct apmregs *regs;
394 {
395 	struct apmregs nregs;
396 	int ret = 0;
397 
398 	switch(regs->bx) {
399 	case APM_NOEVENT:
400 		ret++;
401 		break;
402 
403 	case APM_USER_STANDBY_REQ:
404 		if (apm_resumes || apm_op_inprog)
405 			break;
406 		DPRINTF(("user wants STANDBY--fat chance\n"));
407 		apm_op_inprog++;
408 		if (apm_record_event(sc, regs->bx)) {
409 			DPRINTF(("standby ourselves\n"));
410 			apm_userstandbys++;
411 		}
412 		break;
413 	case APM_STANDBY_REQ:
414 		if (apm_resumes || apm_op_inprog)
415 			break;
416 		DPRINTF(("standby requested\n"));
417 		if (apm_standbys || apm_suspends) {
418 			DPRINTF(("premature standby\n"));
419 			apm_error++;
420 			ret++;
421 		}
422 		apm_op_inprog++;
423 		if (apm_record_event(sc, regs->bx)) {
424 			DPRINTF(("standby ourselves\n"));
425 			apm_standbys++;
426 		}
427 		break;
428 	case APM_USER_SUSPEND_REQ:
429 		if (apm_resumes || apm_op_inprog)
430 			break;
431 		DPRINTF(("user wants suspend--fat chance!\n"));
432 		apm_op_inprog++;
433 		if (apm_record_event(sc, regs->bx)) {
434 			DPRINTF(("suspend ourselves\n"));
435 			apm_suspends++;
436 		}
437 		break;
438 	case APM_SUSPEND_REQ:
439 		if (apm_resumes || apm_op_inprog)
440 			break;
441 		DPRINTF(("suspend requested\n"));
442 		if (apm_standbys || apm_suspends) {
443 			DPRINTF(("premature suspend\n"));
444 			apm_error++;
445 			ret++;
446 		}
447 		apm_op_inprog++;
448 		if (apm_record_event(sc, regs->bx)) {
449 			DPRINTF(("suspend ourselves\n"));
450 			apm_suspends++;
451 		}
452 		break;
453 	case APM_POWER_CHANGE:
454 		DPRINTF(("power status change\n"));
455 		if (apm_get_powstat(&nregs) == 0 &&
456 		    BATT_LIFE(&nregs) != APM_BATT_LIFE_UNKNOWN &&
457 		    BATT_LIFE(&nregs) < cpu_apmwarn &&
458 		    (sc->sc_flags & SCFLAG_PRINT) != SCFLAG_NOPRINT &&
459 		    ((sc->sc_flags & SCFLAG_PRINT) != SCFLAG_PCTPRINT ||
460 		     sc->batt_life != BATT_LIFE(&nregs)))
461 			apm_power_print(sc, &nregs);
462 		apm_record_event(sc, regs->bx);
463 		break;
464 	case APM_NORMAL_RESUME:
465 		DPRINTF(("system resumed\n"));
466 		apm_resume(sc, regs);
467 		break;
468 	case APM_CRIT_RESUME:
469 		DPRINTF(("system resumed without us!\n"));
470 		apm_resume(sc, regs);
471 		break;
472 	case APM_SYS_STANDBY_RESUME:
473 		DPRINTF(("system standby resume\n"));
474 		apm_resume(sc, regs);
475 		break;
476 	case APM_UPDATE_TIME:
477 		DPRINTF(("update time, please\n"));
478 		inittodr(time.tv_sec);
479 		apm_record_event(sc, regs->bx);
480 		break;
481 	case APM_CRIT_SUSPEND_REQ:
482 		DPRINTF(("suspend required immediately\n"));
483 		apm_record_event(sc, regs->bx);
484 		apm_suspend();
485 		break;
486 	case APM_BATTERY_LOW:
487 		DPRINTF(("Battery low!\n"));
488 		apm_battlow++;
489 		apm_record_event(sc, regs->bx);
490 		break;
491 	case APM_CAPABILITY_CHANGE:
492 		DPRINTF(("capability change\n"));
493 		if (apm_minver < 2) {
494 			DPRINTF(("adult event\n"));
495 		} else {
496 			if (apmcall(APM_GET_CAPABILITIES, APM_DEV_APM_BIOS,
497 			    &nregs) != 0) {
498 				apm_perror("get capabilities", &nregs);
499 			} else {
500 				apm_get_powstat(&nregs);
501 			}
502 		}
503 		break;
504 	default: {
505 #ifdef APMDEBUG
506 		char *p;
507 		switch (regs->bx >> 8) {
508 		case 0:	p = "reserved system";	break;
509 		case 1:	p = "reserved device";	break;
510 		case 2:	p = "OEM defined";	break;
511 		default:p = "reserved";		break;
512 		}
513 #endif
514 		DPRINTF(("apm_handle_event: %s event, code %d\n", p, regs->bx));
515 	    }
516 	}
517 
518 	return ret;
519 }
520 
521 int
apm_periodic_check(sc)522 apm_periodic_check(sc)
523 	struct apm_softc *sc;
524 {
525 	struct apmregs regs;
526 	int ret = 0;
527 
528 	if (apm_op_inprog)
529 		apm_set_powstate(APM_DEV_ALLDEVS, APM_LASTREQ_INPROG);
530 
531 	while (1) {
532 		if (apm_get_event(&regs) != 0) {
533 			/* i think some bioses combine the error codes */
534 			if (!(APM_ERR_CODE(&regs) & APM_ERR_NOEVENTS))
535 				apm_perror("get event", &regs);
536 			break;
537 		}
538 
539 		if (apm_handle_event(sc, &regs))
540 			break;
541 	}
542 
543 	if (apm_error || APM_ERR_CODE(&regs) == APM_ERR_NOTCONN)
544 		ret = -1;
545 
546 	if (apm_suspends /*|| (apm_battlow && apm_userstandbys)*/) {
547 		apm_op_inprog = 0;
548 		apm_suspend();
549 	} else if (apm_standbys || apm_userstandbys) {
550 		apm_op_inprog = 0;
551 		apm_standby();
552 	}
553 	apm_suspends = apm_standbys = apm_battlow = apm_userstandbys = 0;
554 	apm_error = 0;
555 
556 	if (apm_resumes)
557 		apm_resumes--;
558 	return (ret);
559 }
560 
561 void
apm_powmgt_enable(onoff)562 apm_powmgt_enable(onoff)
563 	int onoff;
564 {
565 	struct apmregs regs;
566 	bzero(&regs, sizeof(regs));
567 	regs.cx = onoff ? APM_MGT_ENABLE : APM_MGT_DISABLE;
568 	if (apmcall(APM_PWR_MGT_ENABLE,
569 	    (apm_minver? APM_DEV_APM_BIOS : APM_MGT_ALL), &regs) != 0)
570 		apm_perror("power management enable", &regs);
571 }
572 
573 void
apm_powmgt_engage(onoff,dev)574 apm_powmgt_engage(onoff, dev)
575 	int onoff;
576 	u_int dev;
577 {
578 	struct apmregs regs;
579 	if (apm_minver == 0)
580 		return;
581 	bzero(&regs, sizeof(regs));
582 	regs.cx = onoff ? APM_MGT_ENGAGE : APM_MGT_DISENGAGE;
583 	if (apmcall(APM_PWR_MGT_ENGAGE, dev, &regs) != 0)
584 		printf("apm0: APM engage (device %x): %s (%d)\n",
585 		    dev, apm_err_translate(APM_ERR_CODE(&regs)),
586 		    APM_ERR_CODE(&regs));
587 }
588 
589 #ifdef notused
590 void
apm_devpowmgt_enable(onoff,dev)591 apm_devpowmgt_enable(onoff, dev)
592 	int onoff;
593 	u_int dev;
594 {
595 	struct apmregs regs;
596 	if (apm_minver == 0)
597 		return;
598 	/* enable is auto BIOS management.
599 	 * disable is program control.
600 	 */
601 	bzero(&regs, sizeof(regs));
602 	regs.cx = onoff ? APM_MGT_ENABLE : APM_MGT_DISABLE;
603 	if (apmcall(APM_DEVICE_MGMT_ENABLE, dev, &regs) != 0)
604 		printf("APM device engage (device %x): %s (%d)\n",
605 		    dev, apm_err_translate(APM_ERR_CODE(&regs)),
606 		    APM_ERR_CODE(&regs));
607 }
608 #endif
609 
610 int
apm_set_powstate(dev,state)611 apm_set_powstate(dev, state)
612 	u_int dev, state;
613 {
614 	struct apmregs regs;
615 	if (!apm_cd.cd_ndevs || (apm_minver == 0 && state > APM_SYS_OFF))
616 		return EINVAL;
617 	bzero(&regs, sizeof(regs));
618 	regs.cx = state;
619 	if (apmcall(APM_SET_PWR_STATE, dev, &regs) != 0) {
620 		apm_perror("set power state", &regs);
621 		if (APM_ERR_CODE(&regs) == APM_ERR_UNRECOG_DEV)
622 			return ENXIO;
623 		else
624 			return EIO;
625 	}
626 	return 0;
627 }
628 
629 void
apm_cpu_busy(void)630 apm_cpu_busy(void)
631 {
632 	struct apmregs regs;
633 
634 	if (!apm_cd.cd_ndevs)	/* No APM device, punt */
635 		return;
636 	if (!apm_dobusy)
637 		return;
638 	if (!apm_idle_called)
639 		return;
640 
641 	if (apm_flags & APM_IDLE_SLOWS) {
642 		bzero(&regs, sizeof(regs));
643 		if (apmcall(APM_CPU_BUSY, 0, &regs) != 0) {
644 #ifdef DIAGNOSTIC
645 			apm_perror("set CPU busy", &regs);
646 #endif
647 		}
648 		apm_idle_called = 0;
649 	}
650 }
651 
652 void
apm_cpu_idle(void)653 apm_cpu_idle(void)
654 {
655 	struct apmregs regs;
656 	static int call_apm = 0;
657 
658 	if (!apm_cd.cd_ndevs) {	/* No APM device, wait for next interrupt */
659 		__asm __volatile("sti;hlt");
660 		return;
661 	}
662 
663 	if (!apm_doidle) {
664 		__asm __volatile("sti;hlt");
665 		return;
666 	}
667 
668 	/*
669 	 * We call the bios APM_IDLE routine here only when we
670 	 * have been idle for some time - otherwise we just hlt.
671 	 */
672 
673 	if  (call_apm != cp_time[CP_IDLE]) {
674 		/* Always call BIOS halt/idle stuff */
675 		bzero(&regs, sizeof(regs));
676 		if (apmcall(APM_CPU_IDLE, 0, &regs) != 0) {
677 #ifdef APMDEBUG
678 			apm_perror("set CPU idle", &regs);
679 #endif
680 		}
681 		apm_idle_called = 1;
682 		/* If BIOS did halt, don't do it again! */
683 		if (apm_flags & APM_IDLE_SLOWS) {
684 			__asm __volatile("sti;hlt");
685 		}
686 		call_apm = cp_time[CP_IDLE];
687 	} else {
688 		__asm __volatile("sti;hlt");
689 	}
690 }
691 
692 void
apm_set_ver(self)693 apm_set_ver(self)
694 	struct apm_softc *self;
695 {
696 	struct apmregs regs;
697 	int rv = 0;
698 
699 	bzero(&regs, sizeof(regs));
700 	regs.cx = APM_VERSION;
701 
702 	if (APM_MAJOR(apm_flags) == 1 && APM_MINOR(apm_flags) == 2 &&
703 	    (rv = apmcall(APM_DRIVER_VERSION, APM_DEV_APM_BIOS, &regs)) == 0) {
704 		apm_majver = APM_CONN_MAJOR(&regs);
705 		apm_minver = APM_CONN_MINOR(&regs);
706 	} else {
707 #ifdef APMDEBUG
708 		if (rv)
709 			apm_perror("set version 1.2", &regs);
710 #endif
711 		/* try downgrading to 1.1 */
712 		bzero(&regs, sizeof(regs));
713 		regs.cx = 0x0101;
714 
715 		if (apmcall(APM_DRIVER_VERSION, APM_DEV_APM_BIOS, &regs) == 0) {
716 			apm_majver = 1;
717 			apm_minver = 1;
718 		} else {
719 #ifdef APMDEBUG
720 			apm_perror("set version 1.1", &regs);
721 #endif
722 			/* stay w/ flags then */
723 			apm_majver = APM_MAJOR(apm_flags);
724 			apm_minver = APM_MINOR(apm_flags);
725 
726 			/* fix version for some endianess-challenged compaqs */
727 			if (!apm_majver) {
728 				apm_majver = 1;
729 				apm_minver = 0;
730 			}
731 		}
732 	}
733 	printf(": Power Management spec V%d.%d", apm_majver, apm_minver);
734 	if (apm_flags & APM_IDLE_SLOWS) {
735 		DPRINTF((" (slowidle)"));
736 		apm_dobusy = 1;
737 		apm_doidle = 1;
738 	} else {
739 		apm_dobusy = 0;
740 		apm_doidle = 1;
741 	}
742 #ifdef DIAGNOSTIC
743 	if (apm_flags & APM_BIOS_PM_DISABLED)
744 		printf(" (BIOS mgmt disabled)");
745 	if (apm_flags & APM_BIOS_PM_DISENGAGED)
746 		printf(" (BIOS managing devices)");
747 #endif
748 	printf("\n");
749 }
750 
751 void
apm_disconnect(sc)752 apm_disconnect(sc)
753 	struct apm_softc *sc;
754 {
755 	struct apmregs regs;
756 	bzero(&regs, sizeof(regs));
757 	if (apmcall(APM_SYSTEM_DEFAULTS,
758 	    (apm_minver == 1 ? APM_DEV_ALLDEVS : APM_DEFAULTS_ALL), &regs))
759 		apm_perror("system defaults failed", &regs);
760 
761 	if (apmcall(APM_DISCONNECT, APM_DEV_APM_BIOS, &regs))
762 		apm_perror("disconnect failed", &regs);
763 	else
764 		printf("%s: disconnected\n", sc->sc_dev.dv_xname);
765 }
766 
767 int
apmprobe(parent,match,aux)768 apmprobe(parent, match, aux)
769 	struct device *parent;
770 	void *match, *aux;
771 {
772 	struct bios_attach_args *ba = aux;
773 	bios_apminfo_t *ap = ba->bios_apmp;
774 	bus_space_handle_t ch, dh;
775 
776 	if (apm_cd.cd_ndevs ||
777 	    strcmp(ba->bios_dev, "apm") ||
778 	    !(ba->bios_apmp->apm_detail & APM_32BIT_SUPPORTED)) {
779 		DPRINTF(("%s: %x\n", ba->bios_dev, ba->bios_apmp->apm_detail));
780 		return 0;
781 	}
782 
783 	/* addresses check
784 	   since pc* console and vga* probes much later
785 	   we cannot check for video memory being mapped
786 	   for apm stuff w/ bus_space_map() */
787 	if (ap->apm_code_len == 0 ||
788 	    (ap->apm_code32_base < IOM_BEGIN &&
789 	     ap->apm_code32_base + ap->apm_code_len > IOM_BEGIN) ||
790 	    (ap->apm_code16_base < IOM_BEGIN &&
791 	     ap->apm_code16_base + ap->apm_code16_len > IOM_BEGIN) ||
792 	    (ap->apm_data_base < IOM_BEGIN &&
793 	     ap->apm_data_base + ap->apm_data_len > IOM_BEGIN))
794 		return 0;
795 
796 	if (bus_space_map(ba->bios_memt, ap->apm_code32_base,
797 	    ap->apm_code_len, 1, &ch) != 0) {
798 		DPRINTF(("apm0: can't map code\n"));
799 		return 0;
800 	}
801 	bus_space_unmap(ba->bios_memt, ch, ap->apm_code_len);
802 
803 	if (bus_space_map(ba->bios_memt, ap->apm_data_base,
804 	    ap->apm_data_len, 1, &dh) != 0) {
805 		DPRINTF(("apm0: can't map data\n"));
806 		return 0;
807 	}
808 	bus_space_unmap(ba->bios_memt, dh, ap->apm_data_len);
809 
810 	return 1;
811 }
812 
813 void
apmattach(parent,self,aux)814 apmattach(parent, self, aux)
815 	struct device *parent, *self;
816 	void *aux;
817 {
818 	extern union descriptor *dynamic_gdt;
819 	struct bios_attach_args *ba = aux;
820 	bios_apminfo_t *ap = ba->bios_apmp;
821 	struct apm_softc *sc = (void *)self;
822 	struct apmregs regs;
823 	u_int cbase, clen, l;
824 	bus_space_handle_t ch16, ch32, dh;
825 
826 	apm_flags = ap->apm_detail;
827 	/*
828 	 * set up GDT descriptors for APM
829 	 */
830 	if (apm_flags & APM_32BIT_SUPPORTED) {
831 
832 		/* truncate segments' limits to a page */
833 		ap->apm_code_len -= (ap->apm_code32_base +
834 		    ap->apm_code_len + 1) & 0xfff;
835 		ap->apm_code16_len -= (ap->apm_code16_base +
836 		    ap->apm_code16_len + 1) & 0xfff;
837 		ap->apm_data_len -= (ap->apm_data_base +
838 		    ap->apm_data_len + 1) & 0xfff;
839 
840 		/* adjust version */
841 		if ((sc->sc_dev.dv_cfdata->cf_flags & APM_VERMASK) &&
842 		    (apm_flags & APM_VERMASK) !=
843 		    (sc->sc_dev.dv_cfdata->cf_flags & APM_VERMASK))
844 			apm_flags = (apm_flags & ~APM_VERMASK) |
845 			    (sc->sc_dev.dv_cfdata->cf_flags & APM_VERMASK);
846 		if (sc->sc_dev.dv_cfdata->cf_flags & APM_NOCLI) {
847 			extern int apm_cli; /* from apmcall.S */
848 			apm_cli = 0;
849 		}
850 		if (sc->sc_dev.dv_cfdata->cf_flags & APM_BEBATT)
851 			apm_bebatt = 1;
852 		apm_ep.seg = GSEL(GAPM32CODE_SEL,SEL_KPL);
853 		apm_ep.entry = ap->apm_entry;
854 		cbase = min(ap->apm_code32_base, ap->apm_code16_base);
855 		clen = max(ap->apm_code32_base + ap->apm_code_len,
856 			   ap->apm_code16_base + ap->apm_code16_len) - cbase;
857 		if ((cbase <= ap->apm_data_base &&
858 		     cbase + clen >= ap->apm_data_base) ||
859 		    (ap->apm_data_base <= cbase &&
860 		     ap->apm_data_base + ap->apm_data_len >= cbase)) {
861 			l = max(ap->apm_data_base + ap->apm_data_len + 1,
862 				cbase + clen + 1) -
863 			    min(ap->apm_data_base, cbase);
864 			bus_space_map(ba->bios_memt,
865 				min(ap->apm_data_base, cbase),
866 				l, 1, &dh);
867 			ch16 = dh;
868 			if (ap->apm_data_base < cbase)
869 				ch16 += cbase - ap->apm_data_base;
870 			else
871 				dh += ap->apm_data_base - cbase;
872 		} else {
873 
874 			bus_space_map(ba->bios_memt, cbase, clen + 1, 1, &ch16);
875 			bus_space_map(ba->bios_memt, ap->apm_data_base,
876 			    ap->apm_data_len + 1, 1, &dh);
877 		}
878 		ch32 = ch16;
879 		if (ap->apm_code16_base == cbase)
880 			ch32 += ap->apm_code32_base - cbase;
881 		else
882 			ch16 += ap->apm_code16_base - cbase;
883 
884 		setsegment(&dynamic_gdt[GAPM32CODE_SEL].sd, (void *)ch32,
885 			   ap->apm_code_len, SDT_MEMERA, SEL_KPL, 1, 0);
886 		setsegment(&dynamic_gdt[GAPM16CODE_SEL].sd, (void *)ch16,
887 			   ap->apm_code16_len, SDT_MEMERA, SEL_KPL, 0, 0);
888 		setsegment(&dynamic_gdt[GAPMDATA_SEL].sd, (void *)dh,
889 			   ap->apm_data_len, SDT_MEMRWA, SEL_KPL, 1, 0);
890 		DPRINTF((": flags %x code 32:%x/%x[%x] 16:%x/%x[%x] "
891 		       "data %x/%x/%x ep %x (%x:%x)\n%s", apm_flags,
892 		    ap->apm_code32_base, ch32, ap->apm_code_len,
893 		    ap->apm_code16_base, ch16, ap->apm_code16_len,
894 		    ap->apm_data_base, dh, ap->apm_data_len,
895 		    ap->apm_entry, apm_ep.seg, ap->apm_entry+ch32,
896 		    sc->sc_dev.dv_xname));
897 
898 		apm_set_ver(sc);
899 
900 		if (apm_flags & APM_BIOS_PM_DISABLED)
901 			apm_powmgt_enable(1);
902 		/*
903 		 * Engage cooperative power mgt (we get to do it)
904 		 * on all devices (v1.1).
905 		 */
906 		apm_powmgt_engage(1, APM_DEV_ALLDEVS);
907 
908 		bzero(&regs, sizeof(regs));
909 		if (apm_get_powstat(&regs) == 0) {
910 			apm_power_print(sc, &regs);
911 		} else
912 			apm_perror("get power status", &regs);
913 		apm_cpu_busy();
914 
915 		lockinit(&sc->sc_lock, PWAIT, "apmlk", 0, 0);
916 
917 		/*
918 		 * Do a check once, ignoring any errors. This avoids
919 		 * gratuitous APM disconnects on laptops where the first
920 		 * event in the queue (after a boot) is non-recognizable.
921 		 * The IBM ThinkPad 770Z is one of those.
922 		 */
923 		apm_periodic_check(sc);
924 
925 		if (apm_periodic_check(sc) == -1) {
926 			apm_disconnect(sc);
927 			apm_dobusy = apm_doidle = 0;
928 		} else
929 			kthread_create_deferred(apm_thread_create, sc);
930 	} else {
931 		dynamic_gdt[GAPM32CODE_SEL] = dynamic_gdt[GNULL_SEL];
932 		dynamic_gdt[GAPM16CODE_SEL] = dynamic_gdt[GNULL_SEL];
933 		dynamic_gdt[GAPMDATA_SEL] = dynamic_gdt[GNULL_SEL];
934 	}
935 	/* XXX - To go away */
936 	printf("apm0: flags %x dobusy %d doidle %d\n",
937 		apm_flags, apm_dobusy, apm_doidle);
938 }
939 
940 void
apm_thread_create(v)941 apm_thread_create(v)
942 	void *v;
943 {
944 	struct apm_softc *sc = v;
945 	if (kthread_create(apm_thread, sc, &sc->sc_thread,
946 	    "%s", sc->sc_dev.dv_xname)) {
947 		apm_disconnect(sc);
948 		printf("%s: failed to create kernel thread, disabled",
949 		    sc->sc_dev.dv_xname);
950 		apm_dobusy = apm_doidle = 0;
951 	}
952 }
953 
954 void
apm_thread(v)955 apm_thread(v)
956 	void *v;
957 {
958 	struct apm_softc *sc = v;
959 
960 	for (;;) {
961 		APM_LOCK(sc);
962 		(void) apm_periodic_check(sc);
963 		APM_UNLOCK(sc);
964 		tsleep(&lbolt, PWAIT, "apmev", 0);
965 	}
966 }
967 
968 int
apmopen(dev,flag,mode,p)969 apmopen(dev, flag, mode, p)
970 	dev_t dev;
971 	int flag, mode;
972 	struct proc *p;
973 {
974 	struct apm_softc *sc;
975 	int error = 0;
976 
977 	/* apm0 only */
978 	if (!apm_cd.cd_ndevs || APMUNIT(dev) != 0 ||
979 	    !(sc = apm_cd.cd_devs[APMUNIT(dev)]))
980 		return ENXIO;
981 
982 	DPRINTF(("apmopen: dev %d pid %d flag %x mode %x\n",
983 	    APMDEV(dev), p->p_pid, flag, mode));
984 
985 	APM_LOCK(sc);
986 	switch (APMDEV(dev)) {
987 	case APMDEV_CTL:
988 		if (!(flag & FWRITE)) {
989 			error = EINVAL;
990 			break;
991 		}
992 		if (sc->sc_flags & SCFLAG_OWRITE) {
993 			error = EBUSY;
994 			break;
995 		}
996 		sc->sc_flags |= SCFLAG_OWRITE;
997 		break;
998 	case APMDEV_NORMAL:
999 		if (!(flag & FREAD) || (flag & FWRITE)) {
1000 			error = EINVAL;
1001 			break;
1002 		}
1003 		sc->sc_flags |= SCFLAG_OREAD;
1004 		break;
1005 	default:
1006 		error = ENXIO;
1007 		break;
1008 	}
1009 	APM_UNLOCK(sc);
1010 	return error;
1011 }
1012 
1013 int
apmclose(dev,flag,mode,p)1014 apmclose(dev, flag, mode, p)
1015 	dev_t dev;
1016 	int flag, mode;
1017 	struct proc *p;
1018 {
1019 	struct apm_softc *sc;
1020 
1021 	/* apm0 only */
1022 	if (!apm_cd.cd_ndevs || APMUNIT(dev) != 0 ||
1023 	    !(sc = apm_cd.cd_devs[APMUNIT(dev)]))
1024 		return ENXIO;
1025 
1026 	DPRINTF(("apmclose: pid %d flag %x mode %x\n", p->p_pid, flag, mode));
1027 
1028 	APM_LOCK(sc);
1029 	switch (APMDEV(dev)) {
1030 	case APMDEV_CTL:
1031 		sc->sc_flags &= ~SCFLAG_OWRITE;
1032 		break;
1033 	case APMDEV_NORMAL:
1034 		sc->sc_flags &= ~SCFLAG_OREAD;
1035 		break;
1036 	}
1037 	APM_UNLOCK(sc);
1038 	return 0;
1039 }
1040 
1041 int
apmioctl(dev,cmd,data,flag,p)1042 apmioctl(dev, cmd, data, flag, p)
1043 	dev_t dev;
1044 	u_long cmd;
1045 	caddr_t data;
1046 	int flag;
1047 	struct proc *p;
1048 {
1049 	struct apm_softc *sc;
1050 	struct apmregs regs;
1051 	int error = 0;
1052 
1053 	/* apm0 only */
1054 	if (!apm_cd.cd_ndevs || APMUNIT(dev) != 0 ||
1055 	    !(sc = apm_cd.cd_devs[APMUNIT(dev)]))
1056 		return ENXIO;
1057 
1058 	APM_LOCK(sc);
1059 	switch (cmd) {
1060 		/* some ioctl names from linux */
1061 	case APM_IOC_STANDBY:
1062 		if ((flag & FWRITE) == 0)
1063 			error = EBADF;
1064 		else
1065 			apm_userstandbys++;
1066 		break;
1067 	case APM_IOC_SUSPEND:
1068 		if ((flag & FWRITE) == 0)
1069 			error = EBADF;
1070 		else
1071 			apm_suspends++;
1072 		break;
1073 	case APM_IOC_PRN_CTL:
1074 		if ((flag & FWRITE) == 0)
1075 			error = EBADF;
1076 		else {
1077 			int flag = *(int *)data;
1078 			DPRINTF(( "APM_IOC_PRN_CTL: %d\n", flag ));
1079 			switch (flag) {
1080 			case APM_PRINT_ON:	/* enable printing */
1081 				sc->sc_flags &= ~SCFLAG_PRINT;
1082 				break;
1083 			case APM_PRINT_OFF: /* disable printing */
1084 				sc->sc_flags &= ~SCFLAG_PRINT;
1085 				sc->sc_flags |= SCFLAG_NOPRINT;
1086 				break;
1087 			case APM_PRINT_PCT: /* disable some printing */
1088 				sc->sc_flags &= ~SCFLAG_PRINT;
1089 				sc->sc_flags |= SCFLAG_PCTPRINT;
1090 				break;
1091 			default:
1092 				error = EINVAL;
1093 				break;
1094 			}
1095 		}
1096 		break;
1097 	case APM_IOC_DEV_CTL:
1098 		if ((flag & FWRITE) == 0)
1099 			error = EBADF;
1100 		else {
1101 			struct apm_ctl *actl = (struct apm_ctl *)data;
1102 
1103 			bzero(&regs, sizeof(regs));
1104 			if (!apmcall(APM_GET_POWER_STATE, actl->dev, &regs))
1105 				printf("%s: dev %04x state %04x\n",
1106 				    sc->sc_dev.dv_xname, dev, regs.cx);
1107 
1108 			error = apm_set_powstate(actl->dev, actl->mode);
1109 		}
1110 		break;
1111 	case APM_IOC_GETPOWER:
1112 		if (apm_get_powstat(&regs) == 0) {
1113 			struct apm_power_info *powerp =
1114 			    (struct apm_power_info *)data;
1115 
1116 			bzero(powerp, sizeof(*powerp));
1117 			if (BATT_LIFE(&regs) != APM_BATT_LIFE_UNKNOWN)
1118 				powerp->battery_life = BATT_LIFE(&regs);
1119 			powerp->ac_state = AC_STATE(&regs);
1120 			switch (apm_minver) {
1121 			case 0:
1122 				if (!(BATT_FLAGS(&regs) & APM_BATT_FLAG_NOBATTERY))
1123 					powerp->battery_state = BATT_STATE(&regs);
1124 				break;
1125 			case 1:
1126 			default:
1127 				if (BATT_FLAGS(&regs) & APM_BATT_FLAG_HIGH)
1128 					powerp->battery_state = APM_BATT_HIGH;
1129 				else if (BATT_FLAGS(&regs) & APM_BATT_FLAG_LOW)
1130 					powerp->battery_state = APM_BATT_LOW;
1131 				else if (BATT_FLAGS(&regs) & APM_BATT_FLAG_CRITICAL)
1132 					powerp->battery_state = APM_BATT_CRITICAL;
1133 				else if (BATT_FLAGS(&regs) & APM_BATT_FLAG_CHARGING)
1134 					powerp->battery_state = APM_BATT_CHARGING;
1135 				else if (BATT_FLAGS(&regs) & APM_BATT_FLAG_NOBATTERY)
1136 					powerp->battery_state = APM_BATTERY_ABSENT;
1137 				else
1138 					powerp->battery_state = APM_BATT_UNKNOWN;
1139 				if (BATT_REM_VALID(&regs)) {
1140 					powerp->minutes_left = BATT_REMAINING(&regs);
1141 					if (apm_bebatt)
1142 						powerp->minutes_left =
1143 						    swap16(powerp->minutes_left);
1144 				}
1145 			}
1146 		} else {
1147 			apm_perror("ioctl get power status", &regs);
1148 			error = EIO;
1149 		}
1150 		break;
1151 
1152 	default:
1153 		error = ENOTTY;
1154 	}
1155 
1156 	APM_UNLOCK(sc);
1157 	return error;
1158 }
1159 
1160 void
filt_apmrdetach(kn)1161 filt_apmrdetach(kn)
1162 	struct knote *kn;
1163 {
1164 	struct apm_softc *sc = (struct apm_softc *)kn->kn_hook;
1165 
1166 	APM_LOCK(sc);
1167 	SLIST_REMOVE(&sc->sc_note, kn, knote, kn_selnext);
1168 	APM_UNLOCK(sc);
1169 }
1170 
1171 int
filt_apmread(kn,hint)1172 filt_apmread(kn, hint)
1173 	struct knote *kn;
1174 	long hint;
1175 {
1176 	/* XXX weird kqueue_scan() semantics */
1177 	if (hint && !kn->kn_data)
1178 		kn->kn_data = (int)hint;
1179 
1180 	return (1);
1181 }
1182 
1183 int
apmkqfilter(dev,kn)1184 apmkqfilter(dev, kn)
1185 	dev_t dev;
1186 	struct knote *kn;
1187 {
1188 	struct apm_softc *sc;
1189 
1190 	/* apm0 only */
1191 	if (!apm_cd.cd_ndevs || APMUNIT(dev) != 0 ||
1192 	    !(sc = apm_cd.cd_devs[APMUNIT(dev)]))
1193 		return ENXIO;
1194 
1195 	switch (kn->kn_filter) {
1196 	case EVFILT_READ:
1197 		kn->kn_fop = &apmread_filtops;
1198 		break;
1199 	default:
1200 		return (1);
1201 	}
1202 
1203 	kn->kn_hook = (caddr_t)sc;
1204 
1205 	APM_LOCK(sc);
1206 	SLIST_INSERT_HEAD(&sc->sc_note, kn, kn_selnext);
1207 	APM_UNLOCK(sc);
1208 
1209 	return (0);
1210 }
1211