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