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