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