]> git.lizzy.rs Git - plan9front.git/blob - sys/src/9/pc/apm.c
pc/ether*: use 64-bit physical addresses and check pci membar types and sizes
[plan9front.git] / sys / src / 9 / pc / apm.c
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
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
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
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
84 apmwrite(Chan*, void *a, long n, vlong off)
85 {
86         int s, needreset;
87         if(off || n != sizeof apmu)
88                 error("write a Ureg");
89
90         memmove(&apmu, a, sizeof apmu);
91         needreset = apmu.ax==0x5307;    /* set power state */
92         s = splhi();
93         apmfarcall(APMCSEL, ebx, &apmu);
94         if(needreset){
95                 /*
96                  * some BIOS disable the timers. have to
97                  * reset them after suspend.
98                  */
99                 splhi();
100                 i8253reset();
101         }
102         splx(s);
103         return n;
104 }
105
106 void
107 apmlink(void)
108 {
109         ISAConf isa;
110         char *s;
111
112         if(isaconfig("apm", 0, &isa) == 0)
113                 return;
114
115         /*
116          * APM info passed from boot loader.
117          * Now we need to set up the GDT entries for APM.
118          *
119          * AX = 32-bit code segment base address
120          * EBX = 32-bit code segment offset
121          * CX = 16-bit code segment base address
122          * DX = 32-bit data segment base address
123          * ESI = <16-bit code segment length> <32-bit code segment length> (hi then lo)
124          * DI = 32-bit data segment length
125          */
126
127         if(getreg(&ax, &isa, s="ax") < 0
128         || getreg(&ebx, &isa, s="ebx") < 0
129         || getreg(&cx, &isa, s="cx") < 0
130         || getreg(&dx, &isa, s="dx") < 0
131         || getreg(&esi, &isa, s="esi") < 0
132         || getreg(&di, &isa, s="di") < 0){
133                 print("apm: missing register %s\n", s);
134                 return;
135         }
136
137         /*
138          * The NEC Versa SX bios does not report the correct 16-bit code
139          * segment length when loaded directly from mbr -> 9load (as compared
140          * with going through ld.com).  We'll make both code segments 64k-1 bytes.
141          */
142         esi = 0xFFFFFFFF;
143
144         /*
145          * We are required by the BIOS to set up three consecutive segments,
146          * one for the APM 32-bit code, one for the APM 16-bit code, and 
147          * one for the APM data.  The BIOS handler uses the code segment it
148          * get called with to determine the other two segment selector.
149          */
150         setgdt(APMCSEG, ax<<4, ((esi&0xFFFF)-1)&0xFFFF, SEGEXEC|SEGR|SEGD);
151         setgdt(APMCSEG16, cx<<4, ((esi>>16)-1)&0xFFFF, SEGEXEC|SEGR);
152         setgdt(APMDSEG, dx<<4, (di-1)&0xFFFF, SEGDATA|SEGW|SEGD);
153
154         addarchfile("apm", 0660, apmread, apmwrite);
155
156         print("apm: configured cbase %.8lux off %.8lux\n", ax<<4, ebx);
157
158         return;
159 }
160