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