2 * Interface to Advanced Power Management 1.2 BIOS
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.
8 * ACPI might be the answer, but at the moment this is simpler
13 #include "../port/lib.h"
20 extern int apmfarcall(ushort, ulong, Ureg*); /* apmjump.s */
23 getreg(ulong *reg, ISAConf *isa, char *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);
39 * Segment descriptors look like this.
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]
45 * gran is 0 for 1-byte granularity, 1 for 4k granularity
46 * type is 0 for system segment, 1 for code/data.
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
56 setgdt(int sel, ulong base, ulong limit, int flag)
58 if(sel < 0 || sel >= NGDT)
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;
67 static ulong ax, cx, dx, di, ebx, esi;
70 apmread(Chan*, void *a, long n, vlong off)
75 if(n+off > sizeof apmu)
76 n = sizeof apmu - off;
79 memmove(a, (char*)&apmu+off, n);
84 apmwrite(Chan*, void *a, long n, vlong off)
87 if(off || n != sizeof apmu)
88 error("write a Ureg");
90 memmove(&apmu, a, sizeof apmu);
91 needreset = apmu.ax==0x5307; /* set power state */
93 apmfarcall(APMCSEL, ebx, &apmu);
96 * some BIOS disable the timers. have to
97 * reset them after suspend.
112 if(isaconfig("apm", 0, &isa) == 0)
116 * APM info passed from boot loader.
117 * Now we need to set up the GDT entries for APM.
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
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);
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.
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.
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);
154 addarchfile("apm", 0660, apmread, apmwrite);
156 print("apm: configured cbase %.8lux off %.8lux\n", ax<<4, ebx);