plan9fox/sys/src/9/pc/apm.c
cinap_lenrek 347ac6ef58 guesscpuhz(), apm suspend
use fastclock timer (pit2) to measure cpufreq in guesscpuhz(). this
gives a bigger period minimizing the danger of overrun as pit2 runs
at the constant maximum period of 0x10000 ticks. also use smaller
loop increments (1000) and bigger maximum loop upper bound.

move the loops < ... check to the bottom of the loop so we get the
effective count *before* adding the next loop increment.

ilock() while doing measurements in guesscpuhz() to prevent accidents
with other processors reading fastclock or doing guesscpuhz()
in parralel.

export new i8253reset() function for apm to reset the timers after
a apm bios suspend.
2012-09-30 19:41:34 +02:00

161 lines
3.8 KiB
C

/*
* 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, needreset;
if(off || n != sizeof apmu)
error("write a Ureg");
memmove(&apmu, a, sizeof apmu);
needreset = apmu.ax==0x5307; /* set power state */
s = splhi();
apmfarcall(APMCSEL, ebx, &apmu);
if(needreset){
/*
* some BIOS disable the timers. have to
* reset them after suspend.
*/
splhi();
i8253reset();
}
splx(s);
return n;
}
void
apmlink(void)
{
ISAConf isa;
char *s;
if(isaconfig("apm", 0, &isa) == 0)
return;
/*
* 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("apm: configured cbase %.8lux off %.8lux\n", ax<<4, ebx);
return;
}