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; 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 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