apm.c revision 4225
1#define APM_DEBUG 1
2/*
3 * LP (Laptop Package)
4 *
5 * Copyright (c) 1994 by HOSOKAWA, Tatsumi <hosokawa@mt.cs.keio.ac.jp>
6 *
7 * This software may be used, modified, copied, and distributed, in
8 * both source and binary form provided that the above copyright and
9 * these terms are retained. Under no circumstances is the author
10 * responsible for the proper functioning of this software, nor does
11 * the author assume any responsibility for damages incurred with its
12 * use.
13 *
14 * Sep, 1994	Implemented on FreeBSD 1.1.5.1R (Toshiba AVS001WD)
15 *
16 *	$Id: apm.c,v 1.5 1994/10/02 17:40:38 phk Exp $
17 */
18
19#include "apm.h"
20
21#if NAPM > 0
22
23#include <sys/param.h>
24#include "conf.h"
25#include <sys/kernel.h>
26#include <sys/systm.h>
27#include <sys/malloc.h>
28#include <sys/ioctl.h>
29#include <sys/tty.h>
30#include <sys/file.h>
31#include <sys/proc.h>
32#include <sys/vnode.h>
33#include "i386/isa/isa.h"
34#include "i386/isa/isa_device.h"
35#include <machine/apm_bios.h>
36#include <machine/segments.h>
37#include <machine/clock.h>
38#include <vm/vm.h>
39#include <sys/syslog.h>
40#include "apm_setup.h"
41
42/* static data */
43static int	apm_initialized = 0, active = 0, halt_cpu = 1;
44static u_int	minorversion, majorversion;
45static u_int	cs32_base, cs16_base, ds_base;
46static u_int	cs_limit, ds_limit;
47static u_int	cs_entry;
48static u_int	intversion;
49static int	idle_cpu, disabled, disengaged;
50
51#define is_enabled(foo) ((foo) ? "enabled" : "disabled")
52
53/* Map version number to integer (keeps ordering of version numbers) */
54#define INTVERSION(major, minor)	((major)*100 + (minor))
55
56static timeout_t apm_timeout;
57
58/* setup APM GDT discriptors */
59static void
60setup_apm_gdt(u_int code32_base, u_int code16_base, u_int data_base, u_int code_limit, u_int data_limit)
61{
62	/* setup 32bit code segment */
63	gdt_segs[GAPMCODE32_SEL].ssd_base  = code32_base;
64	gdt_segs[GAPMCODE32_SEL].ssd_limit = code_limit;
65
66	/* setup 16bit code segment */
67	gdt_segs[GAPMCODE16_SEL].ssd_base  = code16_base;
68	gdt_segs[GAPMCODE16_SEL].ssd_limit = code_limit;
69
70	/* setup data segment */
71	gdt_segs[GAPMDATA_SEL  ].ssd_base  = data_base;
72	gdt_segs[GAPMDATA_SEL  ].ssd_limit = data_limit;
73
74	/* reflect these changes on physical GDT */
75	ssdtosd(gdt_segs + GAPMCODE32_SEL, gdt + GAPMCODE32_SEL);
76	ssdtosd(gdt_segs + GAPMCODE16_SEL, gdt + GAPMCODE16_SEL);
77	ssdtosd(gdt_segs + GAPMDATA_SEL  , gdt + GAPMDATA_SEL  );
78}
79
80/* 48bit far pointer */
81struct addr48 {
82	u_long		offset;
83	u_short		segment;
84} apm_addr;
85
86int apm_errno;
87
88inline
89int
90apm_int(u_long *eax,u_long *ebx,u_long *ecx)
91{
92	u_long cf;
93	__asm ("pushl	%%ebp
94		pushl	%%edx
95		xorl	%3,%3
96		lcall	_apm_addr
97		jnc	1f
98		incl	%3
99	1:	popl	%%edx
100		popl	%%ebp"
101		: "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=D" (cf)
102		: "0" (*eax),  "1" (*ebx),  "2" (*ecx)
103		);
104	apm_errno = ((*eax) >> 8) & 0xff;
105	return cf;
106}
107
108
109/* enable/disable power management */
110static int
111apm_enable_disable_pm(int enable)
112{
113	u_long eax,ebx,ecx;
114
115	eax = (APM_BIOS<<8) | APM_ENABLEDISABLEPM;
116
117	if (intversion >= INTVERSION(1, 1)) {
118		ebx  = PMDV_ALLDEV;
119	} else {
120		ebx  = 0xffff;	/* APM version 1.0 only */
121	}
122	ecx  = enable;
123	return apm_int(&eax,&ebx,&ecx);
124}
125
126/* Tell APM-BIOS that WE will do 1.1 and see what they say... */
127static void
128apm_driver_version()
129{
130	u_long eax,ebx,ecx,i;
131
132#ifdef APM_DEBUG
133	eax = (APM_BIOS<<8) | APM_INSTCHECK;
134	ebx  = 0x0;
135	ecx  = 0x0101;
136	i = apm_int(&eax,&ebx,&ecx);
137	printf("[%04lx %04lx %04lx %ld %02x]\n",
138		eax,ebx,ecx,i,apm_errno);
139#endif
140
141	eax = (APM_BIOS<<8) | APM_DRVVERSION;
142	ebx  = 0x0;
143	ecx  = 0x0101;
144	if(!apm_int(&eax,&ebx,&ecx))
145		apm_version = eax & 0xffff;
146
147#ifdef APM_DEBUG
148	eax = (APM_BIOS<<8) | APM_INSTCHECK;
149	ebx  = 0x0;
150	ecx  = 0x0101;
151	i = apm_int(&eax,&ebx,&ecx);
152	printf("[%04lx %04lx %04lx %ld %02x]\n",
153		eax,ebx,ecx,i,apm_errno);
154#endif
155}
156
157/* engage/disengage power management (APM 1.1 or later) */
158static int
159apm_engage_disengage_pm(int engage)
160{
161	u_long eax,ebx,ecx,i;
162
163	eax = (APM_BIOS<<8) | APM_ENGAGEDISENGAGEPM;
164	ebx = PMDV_ALLDEV;
165	ecx = engage;
166	i = apm_int(&eax,&ebx,&ecx);
167	return i;
168}
169
170/* get PM event */
171static u_int
172apm_getevent(void)
173{
174	u_long eax,ebx,ecx;
175
176	eax = (APM_BIOS<<8) | APM_GETPMEVENT;
177
178	if (apm_int(&eax,&ebx,&ecx))
179		return PMEV_NOEVENT;
180
181	return ebx & 0xffff;
182}
183
184/* suspend entire system */
185static int
186apm_suspend_system(void)
187{
188	u_long eax,ebx,ecx;
189
190	eax = (APM_BIOS<<8) | APM_SETPWSTATE;
191	ebx = PMDV_ALLDEV;
192	ecx = PMST_SUSPEND;
193
194	if (apm_int(&eax,&ebx,&ecx)) {
195		printf("Entire system suspend failure: errcode = %ld\n",
196			0xff & (eax >> 8));
197		return 1;
198	}
199	return 0;
200}
201
202/* APM Battery low handler */
203static void
204apm_battery_low(void)
205{
206	printf("\007\007 * * * BATTERY IS LOW * * * \007\007");
207}
208
209static struct timeval suspend_time;
210
211static int
212apm_default_resume(void)
213{
214	u_int second, minute, hour;
215	struct timeval resume_time;
216
217	inittodr(0);	/* adjust time to RTC */
218	microtime(&resume_time);
219	second = resume_time.tv_sec - suspend_time.tv_sec;
220	hour = second / 3600;
221	second %= 3600;
222	minute = second / 60;
223	second %= 60;
224	log(LOG_NOTICE, "resumed from suspended mode (slept %02d:%02d:%02d)\n",
225		hour, minute, second);
226	return 0;
227}
228
229static int
230apm_default_suspend(void)
231{
232	int	pl;
233	microtime(&suspend_time);
234	apm_suspend_system();
235	return 0;
236}
237
238/* get APM information */
239static int
240apm_get_info(apm_info_t aip)
241{
242	u_long eax,ebx,ecx;
243
244	eax = (APM_BIOS<<8)|APM_GETPWSTATUS;
245	ebx = PMDV_ALLDEV;
246
247	if (apm_int(&eax,&ebx,&ecx))
248		return 1;
249
250	aip->ai_acline    = (ebx >> 8) & 0xff;
251	aip->ai_batt_stat = ebx & 0xff;
252	aip->ai_batt_life = ecx & 0xff;
253	aip->ai_major     = (u_int)majorversion;
254	aip->ai_minor     = (u_int)minorversion;
255	return 0;
256}
257
258
259static void apm_processevent(void);
260
261/* inform APM BIOS that CPU is idle */
262void
263apm_cpu_idle(void)
264{
265	if (idle_cpu) {
266		if (active) {
267			__asm ("movw $0x5305, %ax; lcall _apm_addr");
268		}
269	}
270	/*
271	 * Some APM implementation halts CPU in BIOS, whenever
272	 * "CPU-idle" function are invoked, but swtch() of
273	 * FreeBSD halts CPU, therefore, CPU is halted twice
274	 * in the sched loop. It makes the interrupt latency
275	 * terribly long and be able to cause a serious problem
276	 * in interrupt processing. We prevent it by removing
277	 * "hlt" operation from swtch() and managed it under
278	 * APM driver.
279	 */
280	if (!active || halt_cpu) {
281		__asm("sti ; hlt");	/* wait for interrupt */
282	}
283}
284
285/* inform APM BIOS that CPU is busy */
286void
287apm_cpu_busy(void)
288{
289	if (idle_cpu && active) {
290		__asm("movw $0x5306, %ax; lcall _apm_addr");
291	}
292}
293
294
295/*
296 * APM timeout routine:
297 *
298 * This routine is automatically called by timer once per second.
299 */
300
301static void
302apm_timeout(void *arg1)
303{
304	apm_processevent();
305	timeout(apm_timeout, NULL, hz ); /* 1 Hz */
306}
307
308/* enable APM BIOS */
309static void
310apm_event_enable(void)
311{
312#ifdef APM_DEBUG
313	printf("called apm_event_enable()\n");
314#endif
315	if (apm_initialized) {
316		active = 1;
317		timeout(apm_timeout, NULL, 2 * hz);
318	}
319}
320
321/* disable APM BIOS */
322static void
323apm_event_disable(void)
324{
325#ifdef APM_DEBUG
326	printf("called apm_event_disable()\n");
327#endif
328	if (apm_initialized) {
329		untimeout(apm_timeout, NULL);
330		active = 0;
331	}
332}
333
334/* halt CPU in scheduling loop */
335static void apm_halt_cpu(void)
336{
337	if (apm_initialized) {
338		halt_cpu = 1;
339	}
340}
341
342/* don't halt CPU in scheduling loop */
343static void apm_not_halt_cpu(void)
344{
345	if (apm_initialized) {
346		halt_cpu = 0;
347	}
348}
349
350/* device driver definitions */
351int apmprobe (struct isa_device *);
352int apmattach(struct isa_device *);
353
354struct isa_driver apmdriver = { apmprobe, apmattach, "apm" };
355
356/*
357 * probe APM (dummy):
358 *
359 * APM probing routine is placed on locore.s and apm_init.S because
360 * this process forces the CPU to turn to real mode or V86 mode.
361 * Current version uses real mode, but on future version, we want
362 * to use V86 mode in APM initialization.
363 */
364
365int
366apmprobe(struct isa_device *dvp)
367{
368	switch (apm_version) {
369	case APMINI_CANTFIND:
370		/* silent */
371		return 0;
372	case APMINI_NOT32BIT:
373		printf("apm%d: 32bit connection is not supported.\n",
374			dvp->id_unit);
375		return 0;
376	case APMINI_CONNECTERR:
377		printf("apm%d: 32-bit connection error.\n", dvp->id_unit);
378		return 0;
379	}
380
381	if ((apm_version & 0xff00) != 0x0100) return 0;
382	if ((apm_version & 0x00f0) >= 0x00a0) return 0;
383	if ((apm_version & 0x000f) >= 0x000a) return 0;
384	return -1;
385}
386
387
388/* Process APM event */
389static void
390apm_processevent(void)
391{
392	int apm_event;
393
394#ifdef APM_DEBUG
395#  define OPMEV_DEBUGMESSAGE(symbol) case symbol: \
396	printf("Original APM Event: " #symbol "\n");
397#else
398#  define OPMEV_DEBUGMESSAGE(symbol) case symbol:
399#endif
400
401	while (1) {
402		apm_event = apm_getevent();
403		if (apm_event == PMEV_NOEVENT)
404			break;
405		switch (apm_event) {
406		    OPMEV_DEBUGMESSAGE(PMEV_STANDBYREQ);
407			apm_default_suspend();
408			break;
409		    OPMEV_DEBUGMESSAGE(PMEV_SUSPENDREQ);
410			apm_default_suspend();
411			break;
412		    OPMEV_DEBUGMESSAGE(PMEV_USERSUSPENDREQ);
413			apm_default_suspend();
414			break;
415
416		    OPMEV_DEBUGMESSAGE(PMEV_CRITSUSPEND);
417			apm_default_suspend();
418			break;
419
420		    OPMEV_DEBUGMESSAGE(PMEV_NORMRESUME);
421			apm_default_resume();
422			break;
423		    OPMEV_DEBUGMESSAGE(PMEV_CRITRESUME);
424			apm_default_resume();
425			break;
426		    OPMEV_DEBUGMESSAGE(PMEV_STANDBYRESUME);
427			apm_default_resume();
428			break;
429
430		    OPMEV_DEBUGMESSAGE(PMEV_BATTERYLOW);
431			apm_battery_low();
432			apm_default_suspend();
433			break;
434
435		    OPMEV_DEBUGMESSAGE(PMEV_POWERSTATECHANGE);
436			break;
437
438		    OPMEV_DEBUGMESSAGE(PMEV_UPDATETIME);
439			inittodr(0);	/* adjust time to RTC */
440			break;
441
442		    default:
443			printf("Unknown Original APM Event 0x%x\n", apm_event);
444			    break;
445		}
446	}
447}
448
449/*
450 * Attach APM:
451 *
452 * Initialize APM driver (APM BIOS itself has been initialized in locore.s)
453 *
454 * Now, unless I'm mad, (not quite ruled out yet), the APM-1.1 spec is bogus:
455 *
456 * Appendix C says under the header "APM 1.0/APM 1.1 Modal BIOS Behavior"
457 * that "When an APM Driver connects with an APM 1.1 BIOS, the APM 1.1 BIOS
458 * will default to an APM 1.0 connection.  After an APM Driver calls the APM
459 * Driver Version function, specifying that it supports APM 1.1, and [sic!]
460 * APM BIOS will change its behavior to an APM 1.1 connection.  If the APM
461 * BIOS is an APM 1.0 BIOS, the APM Driver Version function call will fail,
462 * and the connection will remain an APM 1.0 connection."
463 *
464 * OK so I can establish a 1.0 connection, and then tell that I'm a 1.1
465 * and maybe then the BIOS will tell that it too is a 1.1.
466 * Fine.
467 * Now how will I ever get the segment-limits for instance ?  There is no
468 * way I can see that I can get a 1.1 response back from an "APM Protected
469 * Mode 32-bit Interface Connect" function ???
470 *
471 * Who made this,  Intel and Microsoft ?  -- How did you guess !
472 *
473 * /phk
474 */
475
476int
477apmattach(struct isa_device *dvp)
478{
479
480	/* setup APM parameters */
481	cs32_base = (apm_cs32_base << 4) + KERNBASE;
482	cs16_base = (apm_cs16_base << 4) + KERNBASE;
483	ds_base = (apm_ds_base << 4) + KERNBASE;
484	cs_limit = apm_cs_limit;
485	ds_limit = apm_ds_limit;
486	cs_entry = apm_cs_entry;
487
488	idle_cpu = ((apm_flags & APM_CPUIDLE_SLOW) != 0);
489	disabled = ((apm_flags & APM_DISABLED) != 0);
490	disengaged = ((apm_flags & APM_DISENGAGED) != 0);
491
492	/* print bootstrap messages */
493#ifdef APM_DEBUG
494	printf(" found APM BIOS version %04x\n",  apm_version);
495	printf("apm%d: Code32 0x%08x, Code16 0x%08x, Data 0x%08x\n",
496		dvp->id_unit, cs32_base, cs16_base, ds_base);
497	printf("apm%d: Code entry 0x%08x, Idling CPU %s, Management %s\n",
498		dvp->id_unit, cs_entry, is_enabled(idle_cpu),
499		is_enabled(!disabled));
500	printf("apm%d: CS_limit=%x, DS_limit=%x\n",
501		dvp->id_unit, cs_limit,ds_limit);
502
503#endif /* APM_DEBUG */
504
505	cs_limit = 0xffff;
506	ds_limit = 0xffff;
507
508	/* setup GDT */
509	setup_apm_gdt(cs32_base, cs16_base, ds_base, cs_limit, ds_limit);
510
511	/* setup entry point 48bit pointer */
512	apm_addr.segment = GSEL(GAPMCODE32_SEL, SEL_KPL);
513	apm_addr.offset  = cs_entry;
514
515	/* Try to kick bios into 1.1 mode */
516	apm_driver_version();
517
518	minorversion = ((apm_version & 0x00f0) >>  4) * 10 +
519			((apm_version & 0x000f) >> 0);
520	majorversion = ((apm_version & 0xf000) >> 12) * 10 +
521			((apm_version & 0x0f00) >> 8);
522
523	intversion = INTVERSION(majorversion, minorversion);
524
525	if (intversion >= INTVERSION(1, 1)) {
526		printf("apm%d: Engaged control %s\n",
527			dvp->id_unit, is_enabled(!disengaged));
528	}
529
530	printf(" found APM BIOS version %d.%d\n", majorversion, minorversion);
531	printf("apm%d: Idling CPU %s\n", dvp->id_unit, is_enabled(idle_cpu));
532
533	/* enable power management */
534	if (disabled) {
535		if (apm_enable_disable_pm(1)) {
536			printf("Warning: APM enable function failed! [%x]\n",
537				apm_errno);
538		}
539	}
540
541	/* engage power managment (APM 1.1 or later) */
542	if (intversion >= INTVERSION(1, 1) && disengaged) {
543		if (apm_engage_disengage_pm(1)) {
544			printf("Warning: APM engage function failed [%x]\n",
545				apm_errno);
546		}
547	}
548
549	apm_initialized = 1;
550
551	apm_event_enable();
552
553	return 0;
554}
555
556int
557apmopen(dev_t dev, int flag, int fmt, struct proc *p)
558{
559	if (!apm_initialized) {
560		return ENXIO;
561	}
562	if (minor(dev))
563		return (ENXIO);
564	return 0;
565}
566
567int
568apmclose(dev_t dev, int flag, int fmt, struct proc *p)
569{
570	return 0;
571}
572
573int
574apmioctl(dev_t dev, int cmd, caddr_t addr, int flag, struct proc *p)
575{
576	int error = 0;
577	int pl;
578
579#ifdef APM_DEBUG
580	printf("APM ioctl: minor = %d, cmd = 0x%x\n", minor(dev), cmd);
581#endif
582
583	pl = splhigh();
584	if (minor(dev) != 0) {
585		return ENXIO;
586	}
587	if (!apm_initialized) {
588		return ENXIO;
589	}
590	switch (cmd) {
591	case APMIO_SUSPEND:
592		apm_default_suspend();
593		break;
594	case APMIO_GETINFO:
595		if (apm_get_info((apm_info_t)addr)) {
596			error = ENXIO;
597		}
598		break;
599	case APMIO_ENABLE:
600		apm_event_enable();
601		break;
602	case APMIO_DISABLE:
603		apm_event_disable();
604		break;
605	case APMIO_HALTCPU:
606		apm_halt_cpu();
607		break;
608	case APMIO_NOTHALTCPU:
609		apm_not_halt_cpu();
610		break;
611	default:
612		error = EINVAL;
613		break;
614	}
615	splx(pl);
616	return error;
617}
618
619#endif /* NAPM > 0 */
620