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