diff options
author | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
---|---|---|
committer | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
commit | e5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch) | |
tree | d8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/9/pc/apm.c |
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/9/pc/apm.c')
-rwxr-xr-x | sys/src/9/pc/apm.c | 153 |
1 files changed, 153 insertions, 0 deletions
diff --git a/sys/src/9/pc/apm.c b/sys/src/9/pc/apm.c new file mode 100755 index 000000000..65d6c60bc --- /dev/null +++ b/sys/src/9/pc/apm.c @@ -0,0 +1,153 @@ +/* + * Interface to Advanced Power Management 1.2 BIOS + * + * This is, in many ways, a giant hack, and when things settle down + * a bit and standardize, hopefully we can write a driver that deals + * more directly with the hardware and thus might be a bit cleaner. + * + * ACPI might be the answer, but at the moment this is simpler + * and more widespread. + */ + +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" +#include "ureg.h" + +extern int apmfarcall(ushort, ulong, Ureg*); /* apmjump.s */ + +static int +getreg(ulong *reg, ISAConf *isa, char *name) +{ + int i; + int nl; + + nl = strlen(name); + for(i=0; i<isa->nopt; i++){ + if(cistrncmp(isa->opt[i], name, nl)==0 && isa->opt[i][nl] == '='){ + *reg = strtoul(isa->opt[i]+nl+1, nil, 16); + return 0; + } + } + return -1; +} + +/* + * Segment descriptors look like this. + * + * d1: [base 31:24] [gran] [is32bit] [0] [unused] [limit 19:16] + [present] [privlev] [type 3:0] [base 23:16] + * d0: [base 15:00] [limit 15:00] + * + * gran is 0 for 1-byte granularity, 1 for 4k granularity + * type is 0 for system segment, 1 for code/data. + * + * clearly we know way too much about the memory unit. + * however, knowing this much about the memory unit + * means that the memory unit need not know anything + * about us. + * + * what a crock. + */ +static void +setgdt(int sel, ulong base, ulong limit, int flag) +{ + if(sel < 0 || sel >= NGDT) + panic("setgdt"); + + base = (ulong)KADDR(base); + m->gdt[sel].d0 = (base<<16) | (limit&0xFFFF); + m->gdt[sel].d1 = (base&0xFF000000) | (limit&0x000F0000) | + ((base>>16)&0xFF) | SEGP | SEGPL(0) | flag; +} + +static ulong ax, cx, dx, di, ebx, esi; +static Ureg apmu; +static long +apmread(Chan*, void *a, long n, vlong off) +{ + if(off < 0) + error("badarg"); + + if(n+off > sizeof apmu) + n = sizeof apmu - off; + if(n <= 0) + return 0; + memmove(a, (char*)&apmu+off, n); + return n; +} + +static long +apmwrite(Chan*, void *a, long n, vlong off) +{ + int s; + if(off || n != sizeof apmu) + error("write a Ureg"); + + memmove(&apmu, a, sizeof apmu); + s = splhi(); + apmfarcall(APMCSEL, ebx, &apmu); + splx(s); + return n; +} + +void +apmlink(void) +{ + ISAConf isa; + char *s; + + if(isaconfig("apm", 0, &isa) == 0) + return; + +/* XXX use realmode() */ + + /* + * APM info passed from boot loader. + * Now we need to set up the GDT entries for APM. + * + * AX = 32-bit code segment base address + * EBX = 32-bit code segment offset + * CX = 16-bit code segment base address + * DX = 32-bit data segment base address + * ESI = <16-bit code segment length> <32-bit code segment length> (hi then lo) + * DI = 32-bit data segment length + */ + + if(getreg(&ax, &isa, s="ax") < 0 + || getreg(&ebx, &isa, s="ebx") < 0 + || getreg(&cx, &isa, s="cx") < 0 + || getreg(&dx, &isa, s="dx") < 0 + || getreg(&esi, &isa, s="esi") < 0 + || getreg(&di, &isa, s="di") < 0){ + print("apm: missing register %s\n", s); + return; + } + + /* + * The NEC Versa SX bios does not report the correct 16-bit code + * segment length when loaded directly from mbr -> 9load (as compared + * with going through ld.com). We'll make both code segments 64k-1 bytes. + */ + esi = 0xFFFFFFFF; + + /* + * We are required by the BIOS to set up three consecutive segments, + * one for the APM 32-bit code, one for the APM 16-bit code, and + * one for the APM data. The BIOS handler uses the code segment it + * get called with to determine the other two segment selector. + */ + setgdt(APMCSEG, ax<<4, ((esi&0xFFFF)-1)&0xFFFF, SEGEXEC|SEGR|SEGD); + setgdt(APMCSEG16, cx<<4, ((esi>>16)-1)&0xFFFF, SEGEXEC|SEGR); + setgdt(APMDSEG, dx<<4, (di-1)&0xFFFF, SEGDATA|SEGW|SEGD); + + addarchfile("apm", 0660, apmread, apmwrite); + +print("apm0: configured cbase %.8lux off %.8lux\n", ax<<4, ebx); + + return; +} + |