xref: /plan9-contrib/sys/src/9/pc/apm.c (revision 4de34a7edde43207e841ec91ecd12e6cf5f5ebe7)
1 /*
2  * Interface to Advanced Power Management 1.2 BIOS
3  *
4  * This is, in many ways, a giant hack, and when things settle down
5  * a bit and standardize, hopefully we can write a driver that deals
6  * more directly with the hardware and thus might be a bit cleaner.
7  *
8  * ACPI might be the answer, but at the moment this is simpler
9  * and more widespread.
10  */
11 
12 #include	"u.h"
13 #include	"../port/lib.h"
14 #include	"mem.h"
15 #include	"dat.h"
16 #include	"fns.h"
17 #include	"io.h"
18 #include	"ureg.h"
19 
20 extern int apmfarcall(ushort, ulong, Ureg*);		/* apmjump.s */
21 
22 static int
getreg(ulong * reg,ISAConf * isa,char * name)23 getreg(ulong *reg, ISAConf *isa, char *name)
24 {
25 	int i;
26 	int nl;
27 
28 	nl = strlen(name);
29 	for(i=0; i<isa->nopt; i++){
30 		if(cistrncmp(isa->opt[i], name, nl)==0 && isa->opt[i][nl] == '='){
31 			*reg = strtoul(isa->opt[i]+nl+1, nil, 16);
32 			return 0;
33 		}
34 	}
35 	return -1;
36 }
37 
38 /*
39  * Segment descriptors look like this.
40  *
41  * d1: [base 31:24] [gran] [is32bit] [0] [unused] [limit 19:16]
42 		[present] [privlev] [type 3:0] [base 23:16]
43  * d0: [base 15:00] [limit 15:00]
44  *
45  * gran is 0 for 1-byte granularity, 1 for 4k granularity
46  * type is 0 for system segment, 1 for code/data.
47  *
48  * clearly we know way too much about the memory unit.
49  * however, knowing this much about the memory unit
50  * means that the memory unit need not know anything
51  * about us.
52  *
53  * what a crock.
54  */
55 static void
setgdt(int sel,ulong base,ulong limit,int flag)56 setgdt(int sel, ulong base, ulong limit, int flag)
57 {
58 	if(sel < 0 || sel >= NGDT)
59 		panic("setgdt");
60 
61 	base = (ulong)KADDR(base);
62 	m->gdt[sel].d0 = (base<<16) | (limit&0xFFFF);
63 	m->gdt[sel].d1 = (base&0xFF000000) | (limit&0x000F0000) |
64 			((base>>16)&0xFF) | SEGP | SEGPL(0) | flag;
65 }
66 
67 static	ulong ax, cx, dx, di, ebx, esi;
68 static Ureg apmu;
69 static long
apmread(Chan *,void * a,long n,vlong off)70 apmread(Chan*, void *a, long n, vlong off)
71 {
72 	if(off < 0)
73 		error("badarg");
74 
75 	if(n+off > sizeof apmu)
76 		n = sizeof apmu - off;
77 	if(n <= 0)
78 		return 0;
79 	memmove(a, (char*)&apmu+off, n);
80 	return n;
81 }
82 
83 static long
apmwrite(Chan *,void * a,long n,vlong off)84 apmwrite(Chan*, void *a, long n, vlong off)
85 {
86 	int s;
87 	if(off || n != sizeof apmu)
88 		error("write a Ureg");
89 
90 	memmove(&apmu, a, sizeof apmu);
91 	s = splhi();
92 	apmfarcall(APMCSEL, ebx, &apmu);
93 	splx(s);
94 	return n;
95 }
96 
97 void
apmlink(void)98 apmlink(void)
99 {
100 	ISAConf isa;
101 	char *s;
102 
103 	if(isaconfig("apm", 0, &isa) == 0)
104 		return;
105 
106 /* XXX use realmode() */
107 
108 	/*
109 	 * APM info passed from boot loader.
110 	 * Now we need to set up the GDT entries for APM.
111 	 *
112 	 * AX = 32-bit code segment base address
113 	 * EBX = 32-bit code segment offset
114 	 * CX = 16-bit code segment base address
115 	 * DX = 32-bit data segment base address
116 	 * ESI = <16-bit code segment length> <32-bit code segment length> (hi then lo)
117 	 * DI = 32-bit data segment length
118 	 */
119 
120 	if(getreg(&ax, &isa, s="ax") < 0
121 	|| getreg(&ebx, &isa, s="ebx") < 0
122 	|| getreg(&cx, &isa, s="cx") < 0
123 	|| getreg(&dx, &isa, s="dx") < 0
124 	|| getreg(&esi, &isa, s="esi") < 0
125 	|| getreg(&di, &isa, s="di") < 0){
126 		print("apm: missing register %s\n", s);
127 		return;
128 	}
129 
130 	/*
131 	 * The NEC Versa SX bios does not report the correct 16-bit code
132 	 * segment length when loaded directly from mbr -> 9load (as compared
133 	 * with going through ld.com).  We'll make both code segments 64k-1 bytes.
134 	 */
135 	esi = 0xFFFFFFFF;
136 
137 	/*
138 	 * We are required by the BIOS to set up three consecutive segments,
139 	 * one for the APM 32-bit code, one for the APM 16-bit code, and
140 	 * one for the APM data.  The BIOS handler uses the code segment it
141 	 * get called with to determine the other two segment selector.
142 	 */
143 	setgdt(APMCSEG, ax<<4, ((esi&0xFFFF)-1)&0xFFFF, SEGEXEC|SEGR|SEGD);
144 	setgdt(APMCSEG16, cx<<4, ((esi>>16)-1)&0xFFFF, SEGEXEC|SEGR);
145 	setgdt(APMDSEG, dx<<4, (di-1)&0xFFFF, SEGDATA|SEGW|SEGD);
146 
147 	addarchfile("apm", 0660, apmread, apmwrite);
148 
149 print("apm0: configured cbase %.8lux off %.8lux\n", ax<<4, ebx);
150 
151 	return;
152 }
153 
154