plan 9 kernel history: overview | file list | diff list

2002/0410/pc/i8253.c (diff list | history)

pc/i8253.c on 1997/0327
1997/0327    
#include "u.h" 
#include "../port/lib.h" 
#include "mem.h" 
#include "dat.h" 
#include "fns.h" 
#include "io.h" 
 
/* 
 *  8253 timer 
 */ 
enum 
{ 
	T0cntr=	0x40,		/* counter ports */ 
	T1cntr=	0x41,		/* ... */ 
	T2cntr=	0x42,		/* ... */ 
2000/0622    
	Tmode=	0x43,		/* mode port (control word register) */ 
2000/0623    
	T2ctl=	0x61,		/* counter 2 control port */ 
1997/0327    
 
	/* commands */ 
	Latch0=	0x00,		/* latch counter 0's value */ 
2000/0622    
	Load0l=	0x10,		/* load counter 0's lsb */ 
	Load0m=	0x20,		/* load counter 0's msb */ 
1997/0327    
	Load0=	0x30,		/* load counter 0 with 2 bytes */ 
2002/0405    
 
	Latch1=	0x40,		/* latch counter 1's value */ 
	Load1l=	0x50,		/* load counter 1's lsb */ 
	Load1m=	0x60,		/* load counter 1's msb */ 
	Load1=	0x70,		/* load counter 1 with 2 bytes */ 
 
2000/0623    
	Latch2=	0x80,		/* latch counter 2's value */ 
	Load2l=	0x90,		/* load counter 2's lsb */ 
	Load2m=	0xa0,		/* load counter 2's msb */ 
	Load2=	0xb0,		/* load counter 2 with 2 bytes */ 
1997/0327    
 
2000/0623    
	/* 8254 read-back command: everything > pc-at has an 8254 */ 
	Rdback=	0xc0,		/* readback counters & status */ 
	Rdnstat=0x10,		/* don't read status */ 
	Rdncnt=	0x20,		/* don't read counter value */ 
	Rd0cntr=0x02,		/* read back for which counter */ 
	Rd1cntr=0x04, 
	Rd2cntr=0x08, 
 
1997/0327    
	/* modes */ 
2000/0623    
	ModeMsk=0xe, 
2000/0622    
	Square=	0x6,		/* periodic square wave */ 
2000/0623    
	Trigger=0x0,		/* interrupt on terminal count */ 
	Sstrobe=0x8,		/* software triggered strobe */ 
1997/0327    
 
2002/0409    
	/* T2ctl bits */ 
	T2gate=	(1<<0),		/* enable T2 counting */ 
	T2spkr=	(1<<1),		/* connect T2 out to speaker */ 
	T2out=	(1<<5),		/* output of T2 */ 
 
1997/0327    
	Freq=	1193182,	/* Real clock frequency */ 
2002/0410    
	Tickshift=8,		/* extra accuracy */ 
	MaxPeriod=Freq/HZ, 
	MinPeriod=Freq/(100*HZ), 
1997/0327    
}; 
 
2000/0623    
static struct 
{ 
2000/0627    
	Lock; 
2002/0410    
	ulong	period;		/* current clock period */ 
2000/0701    
	int	enabled; 
2002/0410    
	uvlong	hz; 
2002/0405    
 
2002/0409    
	ushort	last;		/* last value of clock 1 */ 
	uvlong	ticks;		/* cumulative ticks of counter 1 */ 
2000/0623    
}i8253; 
2000/0622    
 
2002/0410    
 
1997/0327    
void 
2002/0405    
i8253init(void) 
1997/0327    
{ 
2002/0405    
	int loops, x; 
1997/0327    
 
2002/0405    
	ioalloc(T0cntr, 4, 0, "i8253"); 
	ioalloc(T2ctl, 1, 0, "i8253.cntr2ctl"); 
2000/0623    
 
2002/0405    
	/* 
2002/0410    
	 *  enable a 1/HZ interrupt for providing scheduling interrupts 
2002/0405    
	 */ 
	outb(Tmode, Load0|Square); 
	outb(T0cntr, (Freq/HZ));	/* low byte */ 
	outb(T0cntr, (Freq/HZ)>>8);	/* high byte */ 
2002/0410    
	i8253.period = Freq/HZ; 
1998/0903    
 
2002/0405    
	/* 
2002/0409    
	 *  enable a longer period counter to use as a clock 
2002/0405    
	 */ 
2002/0409    
	outb(Tmode, Load2|Square); 
	outb(T2cntr, 0);		/* low byte */ 
	outb(T2cntr, 0);		/* high byte */ 
2002/0410    
	i8253.period = Freq/HZ; 
2002/0409    
	x = inb(T2ctl); 
	x |= T2gate; 
	outb(T2ctl, x); 
2002/0410    
	 
2002/0405    
	/* 
	 * Introduce a little delay to make sure the count is 
	 * latched and the timer is counting down; with a fast 
	 * enough processor this may not be the case. 
	 * The i8254 (which this probably is) has a read-back 
	 * command which can be used to make sure the counting 
	 * register has been written into the counting element. 
	 */ 
	x = (Freq/HZ); 
	for(loops = 0; loops < 100000 && x >= (Freq/HZ); loops++){ 
		outb(Tmode, Latch0); 
		x = inb(T0cntr); 
		x |= inb(T0cntr)<<8; 
1997/0327    
	} 
2002/0405    
} 
1997/0327    
 
2002/0405    
void 
guesscpuhz(int aalcycles) 
{ 
	int cpufreq, loops, incr, x, y; 
	vlong a, b; 
 
1997/0327    
	/* find biggest loop that doesn't wrap */ 
	incr = 16000000/(aalcycles*HZ*2); 
	x = 2000; 
	for(loops = incr; loops < 64*1024; loops += incr) { 
	 
		/* 
		 *  measure time for the loop 
		 * 
		 *			MOVL	loops,CX 
		 *	aaml1:	 	AAM 
		 *			LOOP	aaml1 
		 * 
		 *  the time for the loop should be independent of external 
		 *  cache and memory system since it fits in the execution 
		 *  prefetch buffer. 
		 * 
		 */ 
2002/0404    
		outb(Tmode, Latch0); 
2002/0405    
		if(m->havetsc) 
			rdtsc(&a); 
1997/0327    
		x = inb(T0cntr); 
		x |= inb(T0cntr)<<8; 
		aamloop(loops); 
2002/0405    
		outb(Tmode, Latch0); 
		if(m->havetsc) 
2001/1130    
			rdtsc(&b); 
1997/0327    
		y = inb(T0cntr); 
		y |= inb(T0cntr)<<8; 
		x -= y; 
2002/0405    
	 
1997/0327    
		if(x < 0) 
			x += Freq/HZ; 
 
		if(x > Freq/(3*HZ)) 
			break; 
	} 
2002/0405    
 
1997/0327    
	/* 
 	 *  figure out clock frequency and a loop multiplier for delay(). 
1999/0131    
	 *  n.b. counter goes up by 2*Freq 
1997/0327    
	 */ 
1999/0131    
	cpufreq = loops*((aalcycles*2*Freq)/x); 
1997/0327    
	m->loopconst = (cpufreq/1000)/aalcycles;	/* AAM+LOOP's for 1 ms */ 
1999/0131    
 
2002/0405    
	if(m->havetsc){ 
 
1999/0131    
		/* counter goes up by 2*Freq */ 
		b = (b-a)<<1; 
		b *= Freq; 
		b /= x; 
 
		/* 
		 *  round to the nearest megahz 
		 */ 
		m->cpumhz = (b+500000)/1000000L; 
		m->cpuhz = b; 
	} else { 
		/* 
		 *  add in possible 0.5% error and convert to MHz 
		 */ 
		m->cpumhz = (cpufreq + cpufreq/200)/1000000; 
		m->cpuhz = cpufreq; 
	} 
2002/0410    
 
	i8253.hz = Freq<<Tickshift; 
1997/0327    
} 
 
2002/0410    
ulong i8253periodset; 
 
void 
i8253timerset(uvlong next) 
2000/0621    
{ 
2002/0410    
	ulong period; 
	ulong want; 
	ulong now; 
2000/0623    
 
2002/0410    
	want = next>>Tickshift; 
	now = i8253.ticks;	/* assuming whomever called us just did fastticks() */ 
2000/0627    
 
2002/0410    
	period = MaxPeriod; 
	if(next != 0){ 
		period = want - now; 
		if(period < MinPeriod) 
			period = MinPeriod; 
		else if(period > (4*MaxPeriod)/5)	/* strong attraction to MaxPeriod */ 
			period = MaxPeriod; 
2000/0627    
	} 
2000/0623    
 
2002/0410    
	/* histeresis */ 
	if(i8253.period != period){ 
		ilock(&i8253); 
		/* load new value */ 
		outb(Tmode, Load0|Square); 
		outb(T0cntr, period);		/* low byte */ 
		outb(T0cntr, period>>8);	/* high byte */ 
2000/0701    
 
2002/0410    
		/* remember period */ 
		i8253.period = period; 
		i8253periodset++; 
		iunlock(&i8253); 
2000/0701    
	} 
2000/0627    
} 
 
static void 
2002/0410    
i8253clock(Ureg* ureg, void*) 
2000/0627    
{ 
2002/0410    
	timerintr(ureg, 0); 
2000/0621    
} 
 
1997/0327    
void 
i8253enable(void) 
{ 
2000/0701    
	i8253.enabled = 1; 
2002/0410    
	i8253.period = Freq/HZ; 
	intrenable(IrqCLOCK, i8253clock, 0, BUSUNKNOWN, "clock"); 
1997/0327    
} 
1998/0710    
 
/* 
2002/0405    
 *  return the total ticks of counter 1.  We shift by 
 *  8 to give timesync more wriggle room for interpretation 
 *  of the frequency 
1998/0710    
 */ 
uvlong 
i8253read(uvlong *hz) 
{ 
2002/0405    
	ushort y, x; 
	uvlong ticks; 
 
1998/0710    
	if(hz) 
2002/0410    
		*hz = i8253.hz; 
2002/0405    
 
2002/0409    
	ilock(&i8253); 
	outb(Tmode, Latch2); 
	y = inb(T2cntr); 
	y |= inb(T2cntr)<<8; 
2002/0405    
 
2002/0409    
	if(y < i8253.last) 
		x = i8253.last - y; 
2002/0405    
	else 
2002/0410    
		x = i8253.last + (0x10000 - y); 
2002/0409    
	i8253.last = y; 
	i8253.ticks += x>>1; 
	ticks = i8253.ticks; 
	iunlock(&i8253); 
2002/0405    
 
2002/0409    
	return ticks<<Tickshift; 
2002/0405    
} 
 
2002/0410    
void 
delay(int millisecs) 
2002/0405    
{ 
2002/0410    
	millisecs *= m->loopconst; 
	if(millisecs <= 0) 
		millisecs = 1; 
	aamloop(millisecs); 
} 
 
void 
microdelay(int microsecs) 
{ 
	microsecs *= m->loopconst; 
	microsecs /= 1000; 
	if(microsecs <= 0) 
		microsecs = 1; 
	aamloop(microsecs); 
} 
 
ulong 
TK2MS(ulong ticks) 
{ 
	uvlong t, hz; 
 
	t = ticks; 
	hz = HZ; 
	t *= 1000L; 
	t = t/hz; 
	ticks = t; 
	return ticks; 
1998/0710    
} 


source code copyright © 1990-2005 Lucent Technologies; see license
Plan 9 distribution
comments to russ cox (rsc@swtch.com)