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(®s) != 0) {
533 /* i think some bioses combine the error codes */
534 if (!(APM_ERR_CODE(®s) & APM_ERR_NOEVENTS))
535 apm_perror("get event", ®s);
536 break;
537 }
538
539 if (apm_handle_event(sc, ®s))
540 break;
541 }
542
543 if (apm_error || APM_ERR_CODE(®s) == 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(®s, 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), ®s) != 0)
570 apm_perror("power management enable", ®s);
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(®s, sizeof(regs));
582 regs.cx = onoff ? APM_MGT_ENGAGE : APM_MGT_DISENGAGE;
583 if (apmcall(APM_PWR_MGT_ENGAGE, dev, ®s) != 0)
584 printf("apm0: APM engage (device %x): %s (%d)\n",
585 dev, apm_err_translate(APM_ERR_CODE(®s)),
586 APM_ERR_CODE(®s));
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(®s, sizeof(regs));
602 regs.cx = onoff ? APM_MGT_ENABLE : APM_MGT_DISABLE;
603 if (apmcall(APM_DEVICE_MGMT_ENABLE, dev, ®s) != 0)
604 printf("APM device engage (device %x): %s (%d)\n",
605 dev, apm_err_translate(APM_ERR_CODE(®s)),
606 APM_ERR_CODE(®s));
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(®s, sizeof(regs));
618 regs.cx = state;
619 if (apmcall(APM_SET_PWR_STATE, dev, ®s) != 0) {
620 apm_perror("set power state", ®s);
621 if (APM_ERR_CODE(®s) == 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(®s, sizeof(regs));
643 if (apmcall(APM_CPU_BUSY, 0, ®s) != 0) {
644 #ifdef DIAGNOSTIC
645 apm_perror("set CPU busy", ®s);
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(®s, sizeof(regs));
676 if (apmcall(APM_CPU_IDLE, 0, ®s) != 0) {
677 #ifdef APMDEBUG
678 apm_perror("set CPU idle", ®s);
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(®s, 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, ®s)) == 0) {
704 apm_majver = APM_CONN_MAJOR(®s);
705 apm_minver = APM_CONN_MINOR(®s);
706 } else {
707 #ifdef APMDEBUG
708 if (rv)
709 apm_perror("set version 1.2", ®s);
710 #endif
711 /* try downgrading to 1.1 */
712 bzero(®s, sizeof(regs));
713 regs.cx = 0x0101;
714
715 if (apmcall(APM_DRIVER_VERSION, APM_DEV_APM_BIOS, ®s) == 0) {
716 apm_majver = 1;
717 apm_minver = 1;
718 } else {
719 #ifdef APMDEBUG
720 apm_perror("set version 1.1", ®s);
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(®s, sizeof(regs));
757 if (apmcall(APM_SYSTEM_DEFAULTS,
758 (apm_minver == 1 ? APM_DEV_ALLDEVS : APM_DEFAULTS_ALL), ®s))
759 apm_perror("system defaults failed", ®s);
760
761 if (apmcall(APM_DISCONNECT, APM_DEV_APM_BIOS, ®s))
762 apm_perror("disconnect failed", ®s);
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(®s, sizeof(regs));
909 if (apm_get_powstat(®s) == 0) {
910 apm_power_print(sc, ®s);
911 } else
912 apm_perror("get power status", ®s);
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(®s, sizeof(regs));
1104 if (!apmcall(APM_GET_POWER_STATE, actl->dev, ®s))
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(®s) == 0) {
1113 struct apm_power_info *powerp =
1114 (struct apm_power_info *)data;
1115
1116 bzero(powerp, sizeof(*powerp));
1117 if (BATT_LIFE(®s) != APM_BATT_LIFE_UNKNOWN)
1118 powerp->battery_life = BATT_LIFE(®s);
1119 powerp->ac_state = AC_STATE(®s);
1120 switch (apm_minver) {
1121 case 0:
1122 if (!(BATT_FLAGS(®s) & APM_BATT_FLAG_NOBATTERY))
1123 powerp->battery_state = BATT_STATE(®s);
1124 break;
1125 case 1:
1126 default:
1127 if (BATT_FLAGS(®s) & APM_BATT_FLAG_HIGH)
1128 powerp->battery_state = APM_BATT_HIGH;
1129 else if (BATT_FLAGS(®s) & APM_BATT_FLAG_LOW)
1130 powerp->battery_state = APM_BATT_LOW;
1131 else if (BATT_FLAGS(®s) & APM_BATT_FLAG_CRITICAL)
1132 powerp->battery_state = APM_BATT_CRITICAL;
1133 else if (BATT_FLAGS(®s) & APM_BATT_FLAG_CHARGING)
1134 powerp->battery_state = APM_BATT_CHARGING;
1135 else if (BATT_FLAGS(®s) & 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(®s)) {
1140 powerp->minutes_left = BATT_REMAINING(®s);
1141 if (apm_bebatt)
1142 powerp->minutes_left =
1143 swap16(powerp->minutes_left);
1144 }
1145 }
1146 } else {
1147 apm_perror("ioctl get power status", ®s);
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