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