summaryrefslogtreecommitdiff
path: root/sys/src/9/pc/apm.c
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /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-xsys/src/9/pc/apm.c153
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;
+}
+