1/*	$NetBSD: apmbios.c,v 1.18 2011/04/26 15:51:23 joerg Exp $ */
2
3/*-
4 * Copyright (c) 1996, 1997 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by John Kohl and Christopher G. Demetriou.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#include <sys/cdefs.h>
33__KERNEL_RCSID(0, "$NetBSD: apmbios.c,v 1.18 2011/04/26 15:51:23 joerg Exp $");
34
35#include "opt_apm.h"
36
37#ifdef APM_NOIDLE
38#error APM_NOIDLE option deprecated; use APM_NO_IDLE instead
39#endif
40
41#if defined(DEBUG) && !defined(APMDEBUG)
42#define	APMDEBUG
43#endif
44
45#include <sys/param.h>
46#include <sys/systm.h>
47#include <sys/signalvar.h>
48#include <sys/kernel.h>
49#include <sys/proc.h>
50#include <sys/kthread.h>
51#include <sys/malloc.h>
52#include <sys/device.h>
53#include <sys/fcntl.h>
54#include <sys/ioctl.h>
55#include <sys/select.h>
56#include <sys/poll.h>
57#include <sys/conf.h>
58#include <sys/bus.h>
59#include <sys/cpu.h>
60
61#include <uvm/uvm_extern.h>
62
63#include <machine/cpufunc.h>
64#include <machine/gdt.h>
65#include <machine/psl.h>
66
67#include <dev/ic/i8253reg.h>
68#include <dev/isa/isareg.h>
69#include <dev/isa/isavar.h>
70#include <dev/apm/apmvar.h>
71#include <i386/isa/nvram.h>
72
73#include <machine/bioscall.h>
74#ifdef APM_USE_KVM86
75#include <machine/kvm86.h>
76#endif
77#include <machine/apmvar.h>
78
79#if defined(APMDEBUG)
80#define	DPRINTF(f, x)		do { if (apmdebug & (f)) printf x; } while (0)
81#else
82#define	DPRINTF(f, x)
83#endif
84
85static void	apmbiosattach(device_t, device_t, void *);
86static int	apmbiosmatch(device_t, cfdata_t, void *);
87
88#if 0
89static void	apm_devpowmgt_enable(int, u_int);
90#endif
91static void	apm_disconnect(void *);
92static void	apm_enable(void *, int);
93static int	apm_get_powstat(void *, u_int, struct apm_power_info *);
94static int	apm_get_event(void *, u_int *, u_int *);
95static void	apm_cpu_busy(void *);
96static void	apm_cpu_idle(void *);
97static void	apm_get_capabilities(void *, u_int *, u_int *);
98
99struct apm_connect_info apminfo;
100
101static struct apm_accessops apm_accessops = {
102	apm_disconnect,
103	apm_enable,
104	apm_set_powstate,
105	apm_get_powstat,
106	apm_get_event,
107	apm_cpu_busy,
108	apm_cpu_idle,
109	apm_get_capabilities,
110};
111
112extern int apmdebug;
113extern int apm_enabled;
114extern int apm_force_64k_segments;
115extern int apm_allow_bogus_segments;
116extern int apm_bogus_bios;
117extern int apm_minver;
118extern int apm_inited;
119extern int apm_do_idle;
120extern int apm_v12_enabled;
121extern int apm_v11_enabled;
122
123static void	apm_perror(const char *, struct bioscallregs *, ...)
124		    __attribute__((__format__(__printf__,1,3)));
125static void	apm_powmgt_enable(int);
126static void	apm_powmgt_engage(int, u_int);
127static int	apm_get_ver(struct apm_softc *);
128
129CFATTACH_DECL_NEW(apmbios, sizeof(struct apm_softc),
130    apmbiosmatch, apmbiosattach, NULL, NULL);
131
132#ifdef APMDEBUG
133int	apmcall_debug(int, struct bioscallregs *, int);
134static	void acallpr(int, const char *, struct bioscallregs *);
135
136/* bitmask defns for printing apm call args/results */
137#define ACPF_AX		0x00000001
138#define ACPF_AX_HI	0x00000002
139#define ACPF_EAX	0x00000004
140#define ACPF_BX 	0x00000008
141#define ACPF_BX_HI 	0x00000010
142#define ACPF_EBX 	0x00000020
143#define ACPF_CX 	0x00000040
144#define ACPF_CX_HI 	0x00000080
145#define ACPF_ECX 	0x00000100
146#define ACPF_DX 	0x00000200
147#define ACPF_DX_HI 	0x00000400
148#define ACPF_EDX 	0x00000800
149#define ACPF_SI 	0x00001000
150#define ACPF_SI_HI 	0x00002000
151#define ACPF_ESI 	0x00004000
152#define ACPF_DI 	0x00008000
153#define ACPF_DI_HI 	0x00010000
154#define ACPF_EDI 	0x00020000
155#define ACPF_FLAGS 	0x00040000
156#define ACPF_FLAGS_HI	0x00080000
157#define ACPF_EFLAGS 	0x00100000
158
159struct acallinfo {
160	const char *name;
161	int inflag;
162	int outflag;
163};
164
165static struct acallinfo aci[] = {
166  { "install_check", ACPF_BX, ACPF_AX|ACPF_BX|ACPF_CX },
167  { "connectreal", ACPF_BX, 0 },
168  { "connect16", ACPF_BX, ACPF_AX|ACPF_BX|ACPF_CX|ACPF_SI|ACPF_DI },
169  { "connect32", ACPF_BX, ACPF_AX|ACPF_EBX|ACPF_CX|ACPF_DX|ACPF_ESI|ACPF_DI },
170  { "disconnect", ACPF_BX, 0 },
171  { "cpu_idle", 0, 0 },
172  { "cpu_busy", 0, 0 },
173  { "set_power_state", ACPF_BX|ACPF_CX, 0 },
174  { "enable_power_state", ACPF_BX|ACPF_CX, 0 },
175  { "restore_defaults", ACPF_BX, 0 },
176  { "get_power_status", ACPF_BX, ACPF_BX|ACPF_CX|ACPF_DX|ACPF_SI },
177  { "get_event", 0, ACPF_BX|ACPF_CX },
178  { "get_power_state" , ACPF_BX, ACPF_CX },
179  { "enable_dev_power_mgt", ACPF_BX|ACPF_CX, 0 },
180  { "driver_version", ACPF_BX|ACPF_CX, ACPF_AX },
181  { "engage_power_mgt",  ACPF_BX|ACPF_CX, 0 },
182  { "get_caps", ACPF_BX, ACPF_BX|ACPF_CX },
183  { "resume_timer", ACPF_BX|ACPF_CX|ACPF_SI|ACPF_DI, ACPF_CX|ACPF_SI|ACPF_DI },
184  { "resume_ring", ACPF_BX|ACPF_CX, ACPF_CX },
185  { "timer_reqs", ACPF_BX|ACPF_CX, ACPF_CX },
186};
187
188static void acallpr(int flag, const char *tag, struct bioscallregs *b) {
189  if (!flag) return;
190  printf("%s ", tag);
191  if (flag & ACPF_AX) 		printf("ax=%#x ", b->AX);
192  if (flag & ACPF_AX_HI) 	printf("ax_hi=%#x ", b->AX_HI);
193  if (flag & ACPF_EAX) 		printf("eax=%#x ", b->EAX);
194  if (flag & ACPF_BX ) 		printf("bx=%#x ", b->BX);
195  if (flag & ACPF_BX_HI ) 	printf("bx_hi=%#x ", b->BX_HI);
196  if (flag & ACPF_EBX ) 	printf("ebx=%#x ", b->EBX);
197  if (flag & ACPF_CX ) 		printf("cx=%#x ", b->CX);
198  if (flag & ACPF_CX_HI ) 	printf("cx_hi=%#x ", b->CX_HI);
199  if (flag & ACPF_ECX ) 	printf("ecx=%#x ", b->ECX);
200  if (flag & ACPF_DX ) 		printf("dx=%#x ", b->DX);
201  if (flag & ACPF_DX_HI ) 	printf("dx_hi=%#x ", b->DX_HI);
202  if (flag & ACPF_EDX ) 	printf("edx=%#x ", b->EDX);
203  if (flag & ACPF_SI ) 		printf("si=%#x ", b->SI);
204  if (flag & ACPF_SI_HI ) 	printf("si_hi=%#x ", b->SI_HI);
205  if (flag & ACPF_ESI ) 	printf("esi=%#x ", b->ESI);
206  if (flag & ACPF_DI ) 		printf("di=%#x ", b->DI);
207  if (flag & ACPF_DI_HI ) 	printf("di_hi=%#x ", b->DI_HI);
208  if (flag & ACPF_EDI ) 	printf("edi=%#x ", b->EDI);
209  if (flag & ACPF_FLAGS ) 	printf("flags=%#x ", b->FLAGS);
210  if (flag & ACPF_FLAGS_HI) 	printf("flags_hi=%#x ", b->FLAGS_HI);
211  if (flag & ACPF_EFLAGS ) 	printf("eflags=%#x ", b->EFLAGS);
212}
213
214int
215apmcall_debug(int func, struct bioscallregs *regs, int line)
216{
217	int rv;
218	int print = (apmdebug & APMDEBUG_APMCALLS) != 0;
219	const char *name;
220	int inf;
221	int outf = 0; /* XXX: gcc */
222	long long milli;
223
224	if (print) {
225		if (func >= sizeof(aci) / sizeof(aci[0])) {
226			name = 0;
227			inf = outf = 0;
228		} else {
229			name = aci[func].name;
230			inf = aci[func].inflag;
231			outf = aci[func].outflag;
232		}
233		inittodr(time_second);	/* update timestamp */
234		milli = time_second % 1000;
235		if (name)
236			printf("apmcall@%03lld: %s/%#x (line=%d) ",
237			    milli, name, func, line);
238		else
239			printf("apmcall@%03lld: %#x (line=%d) ",
240			    milli, func, line);
241		acallpr(inf, "in:", regs);
242	}
243    	rv = apmcall(func, regs);
244	if (print) {
245		if (rv) {
246			printf(" => error %#x (%s)\n", regs->AX >> 8,
247				apm_strerror(regs->AX >> 8));
248		} else {
249			printf(" => ");
250			acallpr(outf, "out:", regs);
251			printf("\n");
252		}
253	}
254	return (rv);
255}
256
257#define apmcall(f, r)	apmcall_debug((f), (r), __LINE__)
258#endif	/* APMDEBUG */
259
260static void
261apm_perror(const char *str, struct bioscallregs *regs, ...) /* XXX cgd */
262{
263	va_list ap;
264
265	printf("APM ");
266
267	va_start(ap, regs);
268	vprintf(str, ap);			/* XXX cgd */
269	va_end(ap);
270
271	printf(": %s (0x%x)\n", apm_strerror(APM_ERR_CODE(regs)), regs->AX);
272}
273
274static void
275apm_powmgt_enable(int onoff)
276{
277	struct bioscallregs regs;
278
279	regs.BX = apm_minver == 0 ? APM_MGT_ALL : APM_DEV_ALLDEVS;
280	regs.CX = onoff ? APM_MGT_ENABLE : APM_MGT_DISABLE;
281	if (apmcall(APM_PWR_MGT_ENABLE, &regs) != 0)
282		apm_perror("power management enable all <%s>", &regs,
283		    onoff ? "enable" : "disable");
284}
285
286static void
287apm_powmgt_engage(int onoff, u_int dev)
288{
289	struct bioscallregs regs;
290
291	if (apm_minver == 0)
292		return;
293	regs.BX = dev;
294	regs.CX = onoff ? APM_MGT_ENGAGE : APM_MGT_DISENGAGE;
295	if (apmcall(APM_PWR_MGT_ENGAGE, &regs) != 0)
296		apm_perror("power mgmt engage (device %x)", &regs, dev);
297}
298
299#if 0
300static void
301apm_devpowmgt_enable(int onoff, u_int dev)
302{
303	struct bioscallregs regs;
304
305	if (apm_minver == 0)
306	    return;
307	regs.BX = dev;
308
309	/*
310	 * enable is auto BIOS management.
311	 * disable is program control.
312	 */
313	regs.CX = onoff ? APM_MGT_ENABLE : APM_MGT_DISABLE;
314	if (apmcall(APM_DEVICE_MGMT_ENABLE, &regs) != 0)
315		printf("APM device engage (device %x): %s (%d)\n",
316		    dev, apm_strerror(APM_ERR_CODE(&regs)),
317		    APM_ERR_CODE(&regs));
318}
319#endif
320
321
322static int
323apm_get_ver(struct apm_softc *self)
324{
325	struct bioscallregs regs;
326
327	regs.CX = 0x0102;	/* APM Version 1.2 */
328	regs.BX = APM_DEV_APM_BIOS;
329
330	if (apm_v12_enabled && apmcall(APM_DRIVER_VERSION, &regs) == 0)
331		return 0x0102;
332
333	regs.CX = 0x0101;	/* APM Version 1.1 */
334	regs.BX = APM_DEV_APM_BIOS;
335
336	if (apm_v11_enabled && apmcall(APM_DRIVER_VERSION, &regs) == 0)
337		return 0x0101;
338	else
339		return 0x0100;
340}
341
342static int
343apm_get_powstat(void *c, u_int batteryid, struct apm_power_info *pi)
344{
345	struct bioscallregs regs;
346	int error;
347	u_int nbattery = 0, caps;
348
349	if (apm_minver >= 2) {
350		apm_get_capabilities(&regs, &nbattery, &caps);
351		if (batteryid > nbattery)
352			return EIO;
353	} else {
354		if (batteryid > 0)
355			return EIO;
356		nbattery = 0;
357	}
358
359	if (batteryid == 0)
360		regs.BX = APM_DEV_ALLDEVS;
361	else
362		regs.BX = APM_DEV_BATTERY(batteryid);
363
364	if ((error = apmcall(APM_POWER_STATUS, &regs)) != 0)
365		return regs.AX >> 8;
366
367	pi->batteryid = batteryid;
368	pi->nbattery = nbattery;
369	pi->battery_life = APM_BATT_LIFE(&regs);
370	pi->ac_state = APM_AC_STATE(&regs);
371	pi->battery_state = APM_BATT_STATE(&regs);
372	pi->battery_flags = APM_BATT_FLAGS(&regs);
373	pi->minutes_valid = APM_BATT_REM_VALID(&regs);
374	if (pi->minutes_valid)
375		pi->minutes_left = APM_BATT_REMAINING(&regs);
376	else
377		pi->minutes_left = 0;
378	return 0;
379}
380
381/* XXX cgd: this doesn't belong here. */
382#define I386_FLAGBITS "\020\017NT\014OVFL\0130UP\012IEN\011TF\010NF\007ZF\005AF\003PF\001CY"
383
384int
385apm_busprobe(void)
386{
387	struct bioscallregs regs;
388#ifdef APM_USE_KVM86
389	int res;
390#endif
391#ifdef APMDEBUG
392	char bits[128];
393#endif
394
395	memset(&regs, 0, sizeof(struct bioscallregs));
396	regs.AX = APM_BIOS_FN(APM_INSTALLATION_CHECK);
397	regs.BX = APM_DEV_APM_BIOS;
398#ifdef APM_USE_KVM86
399	res = kvm86_bioscall_simple(APM_SYSTEM_BIOS, &regs);
400	if (res) {
401		printf("apm_busprobe: kvm86 error\n");
402		return (0);
403	}
404#else
405	bioscall(APM_SYSTEM_BIOS, &regs);
406#endif
407	DPRINTF(APMDEBUG_PROBE, ("apm: bioscall return: %x %x %x %x %s %x %x\n",
408	    regs.AX, regs.BX, regs.CX, regs.DX,
409	    (snprintb(bits, sizeof(bits), I386_FLAGBITS, regs.EFLAGS), bits),
410	    regs.ESI, regs.EDI));
411
412	if (regs.FLAGS & PSL_C) {
413		DPRINTF(APMDEBUG_PROBE, ("apm: carry set means no APM bios\n"));
414		return 0;	/* no carry -> not installed */
415	}
416	if (regs.BX != APM_INSTALL_SIGNATURE) {
417		DPRINTF(APMDEBUG_PROBE, ("apm: PM signature not found\n"));
418		return 0;
419	}
420	if ((regs.CX & APM_32BIT_SUPPORT) == 0) {
421		DPRINTF(APMDEBUG_PROBE, ("apm: no 32bit support (busprobe)\n"));
422		return 0;
423	}
424
425	return 1;  /* OK to continue probe & complain if something fails */
426}
427
428static int
429apmbiosmatch(device_t parent, cfdata_t match, void *aux)
430{
431	/* There can be only one! */
432	if (apm_inited)
433		return 0;
434
435	/*
436	 * apm_busprobe() said 'go' or we wouldn't be here.
437	 * APM might not be useful (or might be too weird)
438	 * on this machine, but that's handled in attach.
439	 *
440	 * The apm_enabled global variable is used to allow
441	 * users to patch kernels to disable APM support.
442	 */
443	if (apm_enabled) {
444		if (apm_match() == 0) {
445			apm_disconnect(NULL);
446			return 0;
447		} else
448			return 1;
449	}
450	return 0;
451}
452
453#ifdef APMDEBUG
454#define	DPRINTF_BIOSRETURN(regs, bits)					\
455    do {								\
456	    snprintb(bits, sizeof(bits), I386_FLAGBITS, (regs).EFLAGS); \
457	    DPRINTF(APMDEBUG_ATTACH,					\
458		("bioscall return: %x %x %x %x %s %x %x",		\
459		(regs).EAX, (regs).EBX, (regs).ECX, (regs).EDX,		\
460		bits, (regs).ESI, (regs).EDI));				\
461    } while (/*CONSTCOND*/0)
462#else
463#define	DPRINTF_BIOSRETURN(regs, bits)
464#endif
465
466static void
467apmbiosattach(device_t parent, device_t self, void *aux)
468{
469	struct apm_softc *apmsc = device_private(self);
470	struct bioscallregs regs;
471	int apm_data_seg_ok;
472	u_int okbases[] = { 0, biosbasemem*1024 };
473	u_int oklimits[] = { PAGE_SIZE, IOM_END};
474	u_int i;
475	int vers;
476#ifdef APM_USE_KVM86
477	int res;
478#endif
479#ifdef APMDEBUG
480	char bits[128];
481#endif
482
483	aprint_naive(": Power management\n");
484	aprint_normal(": Advanced Power Management BIOS");
485
486	apmsc->sc_dev = self;
487
488	memset(&regs, 0, sizeof(struct bioscallregs));
489	regs.AX = APM_BIOS_FN(APM_INSTALLATION_CHECK);
490	regs.BX = APM_DEV_APM_BIOS;
491#ifdef APM_USE_KVM86
492	res = kvm86_bioscall_simple(APM_SYSTEM_BIOS, &regs);
493	if (res) {
494		aprint_error_dev(self,
495		    "kvm86 error (APM_INSTALLATION_CHECK)\n");
496		goto bail_disconnected;
497	}
498#else
499	bioscall(APM_SYSTEM_BIOS, &regs);
500#endif
501	DPRINTF_BIOSRETURN(regs, bits);
502	DPRINTF(APMDEBUG_ATTACH, ("\n%s: ", device_xname(self)));
503
504	apminfo.apm_detail = (u_int)regs.AX | ((u_int)regs.CX << 16);
505
506	/*
507	 * call a disconnect in case it was already connected
508	 * by some previous code.
509	 */
510	memset(&regs, 0, sizeof(struct bioscallregs));
511	regs.AX = APM_BIOS_FN(APM_DISCONNECT);
512	regs.BX = APM_DEV_APM_BIOS;
513#ifdef APM_USE_KVM86
514	res = kvm86_bioscall_simple(APM_SYSTEM_BIOS, &regs);
515	if (res) {
516		aprint_error_dev(self, "kvm86 error (APM_DISCONNECT)\n");
517		goto bail_disconnected;
518	}
519#else
520	bioscall(APM_SYSTEM_BIOS, &regs);
521#endif
522	DPRINTF_BIOSRETURN(regs, bits);
523	DPRINTF(APMDEBUG_ATTACH, ("\n%s: ", device_xname(self)));
524
525	if ((apminfo.apm_detail & APM_32BIT_SUPPORTED) == 0) {
526		aprint_error_dev(self, "no 32-bit APM support\n");
527		goto bail_disconnected;
528	}
529
530	/*
531	 * And connect to it.
532	 */
533	memset(&regs, 0, sizeof(struct bioscallregs));
534	regs.AX = APM_BIOS_FN(APM_32BIT_CONNECT);
535	regs.BX = APM_DEV_APM_BIOS;
536#ifdef APM_USE_KVM86
537	res = kvm86_bioscall_simple(APM_SYSTEM_BIOS, &regs);
538	if (res) {
539		aprint_error_dev(self,
540		    "kvm86 error (APM_32BIT_CONNECT)\n");
541		goto bail_disconnected;
542	}
543#else
544	bioscall(APM_SYSTEM_BIOS, &regs);
545#endif
546	DPRINTF_BIOSRETURN(regs, bits);
547	DPRINTF(APMDEBUG_ATTACH, ("\n%s: ", device_xname(self)));
548
549	apminfo.apm_code32_seg_base = regs.AX << 4;
550	apminfo.apm_entrypt = regs.BX; /* spec says EBX, can't map >=64k */
551	apminfo.apm_code16_seg_base = regs.CX << 4;
552	apminfo.apm_data_seg_base = regs.DX << 4;
553	apminfo.apm_code32_seg_len = regs.SI;
554	apminfo.apm_code16_seg_len = regs.SI_HI;
555	apminfo.apm_data_seg_len = regs.DI;
556
557	vers = (APM_MAJOR_VERS(apminfo.apm_detail) << 8) +
558	    APM_MINOR_VERS(apminfo.apm_detail);
559	if (apm_force_64k_segments) {
560		apminfo.apm_code32_seg_len = 65536;
561		apminfo.apm_code16_seg_len = 65536;
562		apminfo.apm_data_seg_len = 65536;
563	} else {
564		switch (vers) {
565		case 0x0100:
566			apminfo.apm_code32_seg_len = 65536;
567			apminfo.apm_code16_seg_len = 65536;
568			apminfo.apm_data_seg_len = 65536;
569			apm_v11_enabled = 0;
570			apm_v12_enabled = 0;
571			break;
572		case 0x0101:
573			apminfo.apm_code16_seg_len = apminfo.apm_code32_seg_len;
574			apm_v12_enabled = 0;
575			/* fall through */
576		case 0x0102:
577		default:
578			if (apminfo.apm_code32_seg_len == 0) {
579				/*
580				 * some BIOSes are lame, even if v1.1.
581				 * (Or maybe they want 64k even though they can
582				 * only ask for 64k-1?)
583				 */
584				apminfo.apm_code32_seg_len = 65536;
585				DPRINTF(APMDEBUG_ATTACH,
586				    ("lame v%d.%d bios gave zero len code32, pegged to 64k\n%s: ",
587				    APM_MAJOR_VERS(apminfo.apm_detail),
588				    APM_MINOR_VERS(apminfo.apm_detail),
589				    device_xname(self)));
590			}
591			if (apminfo.apm_code16_seg_len == 0) {
592				/*
593				 * some BIOSes are lame, even if v1.1.
594				 * (Or maybe they want 64k even though they can
595				 * only ask for 64k-1?)
596				 */
597				apminfo.apm_code16_seg_len = 65536;
598				DPRINTF(APMDEBUG_ATTACH,
599				    ("lame v%d.%d bios gave zero len code16, pegged to 64k\n%s: ",
600				    APM_MAJOR_VERS(apminfo.apm_detail),
601				    APM_MINOR_VERS(apminfo.apm_detail),
602				    device_xname(self)));
603			}
604			if (apminfo.apm_data_seg_len == 0) {
605				/*
606				 * some BIOSes are lame, even if v1.1.
607				 *
608				 * leave it alone and assume it does not
609				 * want any sensible data segment
610				 * mapping, and mark as bogus (but with
611				 * expanded size, in case it's in some place
612				 * that costs us nothing to map).
613				 */
614				apm_bogus_bios = 1;
615				apminfo.apm_data_seg_len = 65536;
616				DPRINTF(APMDEBUG_ATTACH,
617				    ("lame v%d.%d bios gave zero len data, tentative 64k\n%s: ",
618				    APM_MAJOR_VERS(apminfo.apm_detail),
619				    APM_MINOR_VERS(apminfo.apm_detail),
620				    device_xname(self)));
621			}
622			break;
623		}
624	}
625	if (apminfo.apm_code32_seg_len < apminfo.apm_entrypt + 4) {
626		DPRINTF(APMDEBUG_ATTACH,
627		    ("nonsensical BIOS code length %d ignored (entry point offset is %d)\n%s: ",
628		    apminfo.apm_code32_seg_len,
629		    apminfo.apm_entrypt,
630		    device_xname(self)));
631		apminfo.apm_code32_seg_len = 65536;
632	}
633	if (apminfo.apm_code32_seg_base < IOM_BEGIN ||
634	    apminfo.apm_code32_seg_base >= IOM_END) {
635		DPRINTF(APMDEBUG_ATTACH, ("code32 segment starts outside ISA hole [%x]\n%s: ",
636		    apminfo.apm_code32_seg_base, device_xname(self)));
637		aprint_error_dev(self,
638		    "bogus 32-bit code segment start\n");
639		goto bail;
640	}
641	if (apminfo.apm_code32_seg_base +
642	    apminfo.apm_code32_seg_len > IOM_END) {
643		DPRINTF(APMDEBUG_ATTACH, ("code32 segment oversized: [%x,%x)\n%s: ",
644		    apminfo.apm_code32_seg_base,
645		    apminfo.apm_code32_seg_base + apminfo.apm_code32_seg_len - 1,
646		    device_xname(self)));
647#if 0
648		aprint_error_dev(self, "bogus 32-bit code segment size\n");
649		goto bail;
650#else
651		apminfo.apm_code32_seg_len =
652		    IOM_END - apminfo.apm_code32_seg_base;
653#endif
654	}
655	if (apminfo.apm_code16_seg_base < IOM_BEGIN ||
656	    apminfo.apm_code16_seg_base >= IOM_END) {
657		DPRINTF(APMDEBUG_ATTACH, ("code16 segment starts outside ISA hole [%x]\n%s: ",
658		    apminfo.apm_code16_seg_base, device_xname(self)));
659		aprint_error_dev(self,
660		    "bogus 16-bit code segment start\n");
661		goto bail;
662	}
663	if (apminfo.apm_code16_seg_base +
664	    apminfo.apm_code16_seg_len > IOM_END) {
665		DPRINTF(APMDEBUG_ATTACH,
666		    ("code16 segment oversized: [%x,%x), giving up\n%s: ",
667		    apminfo.apm_code16_seg_base,
668		    apminfo.apm_code16_seg_base + apminfo.apm_code16_seg_len - 1,
669		    device_xname(self)));
670		/*
671		 * give up since we may have to trash the
672		 * 32bit segment length otherwise.
673		 */
674		aprint_error_dev(self, "bogus 16-bit code segment size\n");
675		goto bail;
676	}
677	/*
678	 * allow data segment to be zero length, within ISA hole or
679	 * at page zero or above biosbasemem and below ISA hole end.
680	 * truncate it if it doesn't quite fit in the space
681	 * we allow.
682	 *
683	 * Otherwise, give up if not "apm_bogus_bios".
684	 */
685	apm_data_seg_ok = 0;
686	for (i = 0; i < 2; i++) {
687		if (apminfo.apm_data_seg_base >= okbases[i] &&
688		    apminfo.apm_data_seg_base < oklimits[i]-1) {
689			/* starts OK */
690			if (apminfo.apm_data_seg_base +
691			    apminfo.apm_data_seg_len > oklimits[i]) {
692				DPRINTF(APMDEBUG_ATTACH,
693				    ("data segment oversized: [%x,%x)",
694				    apminfo.apm_data_seg_base,
695				    apminfo.apm_data_seg_base + apminfo.apm_data_seg_len));
696				apminfo.apm_data_seg_len =
697				    oklimits[i] - apminfo.apm_data_seg_base;
698				DPRINTF(APMDEBUG_ATTACH,
699				    ("; resized to [%x,%x)\n%s: ",
700				    apminfo.apm_data_seg_base,
701				    apminfo.apm_data_seg_base + apminfo.apm_data_seg_len,
702				    device_xname(self)));
703			} else {
704				DPRINTF(APMDEBUG_ATTACH,
705				    ("data segment fine: [%x,%x)\n%s: ",
706				    apminfo.apm_data_seg_base,
707				    apminfo.apm_data_seg_base + apminfo.apm_data_seg_len,
708				    device_xname(self)));
709			}
710			apm_data_seg_ok = 1;
711			break;
712		}
713	}
714	if (!apm_data_seg_ok && apm_bogus_bios) {
715		if (apm_allow_bogus_segments) {
716			DPRINTF(APMDEBUG_ATTACH,
717			    ("bogus bios data seg location, continuing\n%s: ",
718			    device_xname(self)));
719		} else {
720			DPRINTF(APMDEBUG_ATTACH,
721			    ("bogus bios data seg location, ignoring\n%s: ",
722			    device_xname(self)));
723			apminfo.apm_data_seg_base = 0;
724			apminfo.apm_data_seg_len = 0;
725		}
726		apm_data_seg_ok = 1;		/* who are we kidding?! */
727	}
728	if (!apm_data_seg_ok) {
729		DPRINTF(APMDEBUG_ATTACH,
730		    ("data segment [%x,%x) not in an available location\n%s: ",
731		    apminfo.apm_data_seg_base,
732		    apminfo.apm_data_seg_base + apminfo.apm_data_seg_len,
733		    device_xname(self)));
734		aprint_error_dev(self, "data segment unavailable\n");
735		goto bail;
736	}
737
738	/*
739	 * set up GDT descriptors for APM
740	 */
741	apminfo.apm_segsel = GSEL(GAPM32CODE_SEL,SEL_KPL);
742
743	/*
744	 * Some bogus APM V1.1 BIOSes do not return any
745	 * size limits in the registers they are supposed to.
746	 * We forced them to zero before calling the BIOS
747	 * (see apm_init.S), so if we see zero limits here
748	 * we assume that means they should be 64k (and trimmed
749	 * if needed for legitimate memory needs).
750	 */
751	DPRINTF(APMDEBUG_ATTACH, ("code32len=%x, datalen=%x\n%s: ",
752	    apminfo.apm_code32_seg_len,
753	    apminfo.apm_data_seg_len,
754	    device_xname(self)));
755	setgdt(GAPM32CODE_SEL, ISA_HOLE_VADDR(apminfo.apm_code32_seg_base),
756	    apminfo.apm_code32_seg_len - 1,
757	    SDT_MEMERA, SEL_KPL, 1, 0);
758#ifdef GAPM16CODE_SEL
759	setgdt(GAPM16CODE_SEL, ISA_HOLE_VADDR(apminfo.apm_code16_seg_base),
760	    apminfo.apm_code16_seg_len - 1,
761	    SDT_MEMERA, SEL_KPL, 0, 0);
762#endif
763	if (apminfo.apm_data_seg_len == 0) {
764		/*
765		 *if no data area needed, set up the segment
766		 * descriptor to just the first byte of the code
767		 * segment, read only.
768		 */
769		setgdt(GAPMDATA_SEL,
770		    ISA_HOLE_VADDR(apminfo.apm_code32_seg_base),
771		    0, SDT_MEMROA, SEL_KPL, 0, 0);
772	} else if (apminfo.apm_data_seg_base < IOM_BEGIN) {
773		bus_space_handle_t memh;
774
775		/*
776		 * need page zero or biosbasemem area mapping.
777		 *
778		 * XXX cheat and use knowledge of bus_space_map()
779		 * implementation on i386 so it can be done without
780		 * extent checking.
781		 */
782		if (_x86_memio_map(x86_bus_space_mem,
783		    apminfo.apm_data_seg_base,
784		    apminfo.apm_data_seg_len, 0, &memh)) {
785			aprint_error_dev(self,
786			    "couldn't map data segment\n");
787			goto bail;
788		}
789		DPRINTF(APMDEBUG_ATTACH,
790		    ("mapping bios data area %x @ 0x%lx\n%s: ",
791		    apminfo.apm_data_seg_base, memh,
792		    device_xname(self)));
793		setgdt(GAPMDATA_SEL, (void *)memh,
794		    apminfo.apm_data_seg_len - 1,
795		    SDT_MEMRWA, SEL_KPL, 1, 0);
796	} else
797		setgdt(GAPMDATA_SEL, ISA_HOLE_VADDR(apminfo.apm_data_seg_base),
798		    apminfo.apm_data_seg_len - 1,
799		    SDT_MEMRWA, SEL_KPL, 1, 0);
800
801	DPRINTF(APMDEBUG_ATTACH,
802	    ("detail %x 32b:%x/%p/%x 16b:%x/%p/%x data %x/%p/%x ep %x (%x:%p) %p\n%s: ",
803	    apminfo.apm_detail,
804	    apminfo.apm_code32_seg_base,
805	    ISA_HOLE_VADDR(apminfo.apm_code32_seg_base),
806	    apminfo.apm_code32_seg_len,
807	    apminfo.apm_code16_seg_base,
808	    ISA_HOLE_VADDR(apminfo.apm_code16_seg_base),
809	    apminfo.apm_code16_seg_len,
810	    apminfo.apm_data_seg_base,
811	    ISA_HOLE_VADDR(apminfo.apm_data_seg_base),
812	    apminfo.apm_data_seg_len,
813	    apminfo.apm_entrypt,
814	    apminfo.apm_segsel,
815	    apminfo.apm_entrypt +
816	     (char *)ISA_HOLE_VADDR(apminfo.apm_code32_seg_base),
817	    &apminfo.apm_segsel,
818	    device_xname(self)));
819
820	apmsc->sc_ops = &apm_accessops;
821	apmsc->sc_cookie = apmsc;
822	apmsc->sc_vers = apm_get_ver(apmsc);
823	apmsc->sc_detail = apminfo.apm_detail;
824	apmsc->sc_hwflags = 0;
825	apm_attach(apmsc);
826
827	return;
828
829bail:
830	/*
831	 * call a disconnect; we're punting.
832	 */
833	apm_disconnect(apmsc);
834bail_disconnected:
835	aprint_normal_dev(self, "kernel APM support disabled\n");
836}
837
838static void
839apm_enable(void *sc, int on)
840{
841	/*
842	 * XXX some bogus APM BIOSes don't set the disabled bit in
843	 * the connect state, yet still complain about the functions
844	 * being disabled when other calls are made.  sigh.
845	 */
846	if (apminfo.apm_detail & APM_BIOS_PM_DISABLED)
847		apm_powmgt_enable(on);
848
849	/*
850	 * Engage cooperative power mgt (we get to do it)
851	 * on all devices (v1.1).
852	 */
853	apm_powmgt_engage(on, APM_DEV_ALLDEVS);
854}
855
856void
857apm_disconnect(void *arg)
858{
859	struct apm_softc *sc = arg;
860	struct bioscallregs regs;
861#ifdef APMDEBUG
862	char bits[128];
863#endif
864	/*
865	 * We were unable to create the APM thread; bail out.
866	 */
867	memset(&regs, 0, sizeof(struct bioscallregs));
868	regs.AX = APM_BIOS_FN(APM_DISCONNECT);
869	regs.BX = APM_DEV_APM_BIOS;
870#ifdef APM_USE_KVM86
871	(void)kvm86_bioscall_simple(APM_SYSTEM_BIOS, &regs);
872#else
873	bioscall(APM_SYSTEM_BIOS, &regs);
874#endif
875	if (sc == NULL)
876		return;
877	DPRINTF(APMDEBUG_ATTACH, ("\n%s: ", device_xname(sc->sc_dev)));
878	DPRINTF_BIOSRETURN(regs, bits);
879	aprint_error_dev(sc->sc_dev,
880	    "unable to create thread, kernel APM support disabled\n");
881}
882
883static int
884apm_get_event(void *sc, u_int *event_code, u_int *event_info)
885{
886	int error;
887	struct bioscallregs regs;
888
889	if ((error = apmcall(APM_GET_PM_EVENT, &regs)) != 0)
890		return regs.AX >> 8;
891	*event_code = regs.BX;
892	*event_info = regs.AX;
893	return 0;
894}
895
896int
897apm_set_powstate(void *sc, u_int dev, u_int state)
898{
899	struct bioscallregs regs;
900	if (!apm_inited || (apm_minver == 0 && state > APM_SYS_OFF))
901		return EINVAL;
902
903	regs.BX = dev;
904	regs.CX = state;
905	if (apmcall(APM_SET_PWR_STATE, &regs) != 0) {
906		apm_perror("set power state <%x,%x>", &regs, dev, state);
907		return regs.AX >> 8;
908	}
909	return 0;
910}
911
912static void
913apm_cpu_busy(void *sc)
914{
915	struct bioscallregs regs;
916
917	if (!apm_inited || !apm_do_idle)
918	    return;
919	if ((apminfo.apm_detail & APM_IDLE_SLOWS) &&
920	    apmcall(APM_CPU_BUSY, &regs) != 0) {
921		/*
922		 * XXX BIOSes use to set carry without valid
923		 * error number
924		 */
925#ifdef APMDEBUG
926		apm_perror("set CPU busy", &regs);
927#endif
928	}
929}
930
931static void
932apm_cpu_idle(void *sc)
933{
934	struct bioscallregs regs;
935
936	if (!apm_inited || !apm_do_idle)
937	    return;
938	if (apmcall(APM_CPU_IDLE, &regs) != 0) {
939		/*
940		 * XXX BIOSes use to set carry without valid
941		 * error number
942		 */
943#ifdef APMDEBUG
944		apm_perror("set CPU idle", &regs);
945#endif
946	}
947}
948
949/* V1.2 */
950static void
951apm_get_capabilities(void *sc, u_int *numbatts, u_int *capflags)
952{
953	struct bioscallregs regs;
954	int error;
955
956	regs.BX = APM_DEV_APM_BIOS;
957	if ((error = apmcall(APM_GET_CAPABILITIES, &regs)) != 0) {
958		apm_perror("get capabilities", &regs);
959		return;
960	}
961	*capflags = regs.CX;
962	*numbatts = APM_NBATTERIES(&regs);
963
964#ifdef APMDEBUG
965	/* print out stats */
966	DPRINTF(APMDEBUG_INFO, ("apm: %d batteries", *numbatts));
967	if (*capflags & APM_GLOBAL_STANDBY)
968	    DPRINTF(APMDEBUG_INFO, (", global standby"));
969	if (*capflags & APM_GLOBAL_SUSPEND)
970	    DPRINTF(APMDEBUG_INFO, (", global suspend"));
971	if (*capflags & APM_RTIMER_STANDBY)
972	    DPRINTF(APMDEBUG_INFO, (", rtimer standby"));
973	if (*capflags & APM_RTIMER_SUSPEND)
974	    DPRINTF(APMDEBUG_INFO, (", rtimer suspend"));
975	if (*capflags & APM_IRRING_STANDBY)
976	    DPRINTF(APMDEBUG_INFO, (", internal standby"));
977	if (*capflags & APM_IRRING_SUSPEND)
978	    DPRINTF(APMDEBUG_INFO, (", internal suspend"));
979	if (*capflags & APM_PCRING_STANDBY)
980	    DPRINTF(APMDEBUG_INFO, (", pccard standby"));
981	if (*capflags & APM_PCRING_SUSPEND)
982	    DPRINTF(APMDEBUG_INFO, (", pccard suspend"));
983	DPRINTF(APMDEBUG_INFO, ("\n"));
984#endif
985}
986
987#undef DPRINTF_BIOSRETURN
988