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