Statistics

Members: 1927
News: 293
Web Links: 1
Visitors: 3962090

Who's Online

We have 1 guest online
Damn Vulnerable LinuxDamn Vulnerable Linux (DVL) is a Linux-based (modified Damn Small Linux) tool for IT-Security & IT-Anti- Security and Attack & Defense. [CLICK HERE FOR MORE INFOS! ]

Featured Conference Video

T16-Recon2006-Joe_Stewart-OllyBonE.gif OllyBone - Semi-Automatic Unpacking on IA-32. View the conference video here!
Timing with the 8254 PIT
User Rating: / 1
PoorBest 
Written by Jan Verhoeven   


Some time ago I saw a note on the mailinglist from someone in need for a flexible timer function. For this, there are several concepts.

 

 

First, there is the timertick which is updated every 55 ms. For long time delays, this is the best method. Just read the timervalue at 0000:046C, add the desired delay (in 55 ms intervals) and wait until the timer reaches that value.

A second approach is to use modern BIOS-ses which have a timingfunction in BIOS interrupt 15h, but this is "only" present on machines from 1990 or later.

A third approach is to reprogram the RTC chip. No big deal, and there's a very accurate timer in it (upto 8 kHz) which even has interrupt capabillities for automated functions and simple multitaskings.

But by far the best way (and most universal and accurate) is to use the "spare" timer in your PC's 8254 chip.

This chip can be put in many operating modes, but we want it to do the following:

  • start counting at a certain value
  • count down
  • latched reading mode
  • no influence on further PC operation

The counting sequence for the PC is as follows:

  • there are 2^16 BIOS-timervalue updates per hour
  • there are 2^16 8254 clockpulses per timertick

So, there are 2^32 clockpulses per hour. This boils down to one clock pulse being around 838 ns. Not bad.

In order to make things very clear I use Modula-2 to show how the routines are coded. Modula is an extremely structured language, so I use it as a kind of Meta-Assembler or Pseudo-Assembler. For those not too familiar with Modula: a CARDINAL is not an old man in a dress, but a 16 bit unsigned integer.

Here comes.....

---------- OpenTimer ---------------------------- Start ----------

PROCEDURE OpenTimer; (* open timer chip in mode 2 *)

BEGIN

ASM

        MOV  AL, 34H
OUT  43H, AL
XOR  AL, AL
OUT  40H, AL
OUT  40H, AL

END;
END OpenTimer;

---------- OpenTimer ----------------------------- End -----------

The value 34h is constructed as follows:

        bit     function
-----    ---------------------------
6 - 7    select counter (0 - 3)
4 - 5    Read/write mode
1 - 3    Select countermode
0      Binary or BCD

For this case we selected:

  • counter 00
  • read/write two bytes from/to counterchip
  • Mode 2
  • binary values

These few lines open the timer in "Mode 2" and prime the down counting register to 0000. I would love to elaborate on the code, but this is all which is needed....

It is kind of handy if you restore the state of your machine after your application stops using the CPU. Therefore there is the following function to restore "normal" operation of this channel.

---------- CloseTimer --------------------------- Start ----------

PROCEDURE CloseTimer; (* close timer chip *)

BEGIN

ASM

        MOV  AL, 36H
OUT  43H, AL
XOR  AL, AL
OUT  40H, AL
OUT  40H, AL

END;
END CloseTimer;

---------- CloseTimer ---------------------------- End -----------

This function just restores the timer to it's default mode and clears the counting registers. The value "36h" means:

  • counter 00
  • read/write two bytes from/to counterchip
  • Mode 3
  • binary values

---------- ReadTimer ---------------------------- Start ----------

PROCEDURE ReadTimer () : CARDINAL; (* read timer *)

VAR Time : CARDINAL;

BEGIN

ASM

        MOV  AL, 6
OUT  43H, AL
IN   AL, 40H
MOV  AH, AL
IN   AL, 40H
XCHG AH, AL
MOV  [Time], AX

END;
RETURN Time;
END ReadTimer;

---------- ReadTimer ----------------------------- End -----------

After we opened the timer, it might be a good idea to also use it. This is done in a two-step operation:

  • current value of counting register is stored in On-Chip buffer
  • the low byte is read in first
  • the high byte is read in second
  • low and high byte are put in right order

Make sure you always read in TWO bytes, else you will run into framing errors. Also keep in mind that this is a DOWN-COUNTER!

The value "6" which is sent to the 8254 first might be wrong, but in all my software it just works fine. It selects Channel 0 to be latched. The lower four bits of this word should be "don't care" bits, but I prefer "not to fix a running program".

---------- MilliSeconds ------------------------- Start ----------

PROCEDURE MilliSeconds (ms : CARDINAL);

VAR MaxCount : CARDINAL;

BEGIN

MaxCount := 65535 - ms * 1193;
OpenTimer;
WHILE ReadTimer () > MaxCount DO

(* Nothing! *)
END;
CloseTimer;
END MilliSeconds;

---------- MilliSeconds -------------------------- End -----------

This function has some deliberate errors inside. I calculate MaxCount such that it is too big. Reason: in Modula I do not control math operations as well as in ASM (of course!) That's why I subtract the value from 65,535 instead of 65,536. In ASM I would have used a NOT operation, but for Modula this is good enough.

Furthermore I use the number 1193 to go from counting pulses to milliseconds. It's a not too big number so it is good enough to use in integer arithmatics.

This "MilliSeconds" routine is a dumb waiting-procedure. It calculates a stop-value for the counter, initialises the counter to mode 2 and value 0000 and then waits until the timer reaches there. Next it closes the timer and it's all over.

The next function, which was made for diagnostic purposes, shows that in an application you would have to correct for the

---------- TestTimer ---------------------------- Start ----------

PROCEDURE TestTimer;

VAR First, Last, Delta, k : CARDINAL;

BEGIN

OpenTimer;
First := ReadTimer ();
WriteCard (First, 6); Write (Tab); FOR k := 1 TO 10000 DO

(* Nothing! *)
END;
Last := ReadTimer ();
Delta := First - Last;
WriteCard (Delta, 6); WriteLn; CloseTimer;
END TestTimer;

---------- TestTimer ----------------------------- End -----------

You could use this routine to calibrate a timingloop, but on modern PC architectures this could well lead to disasters. Modern CPU's are so damned fast, that your loopcounter will overflow. Therefore this calibration technique is only useful for modifying inherently slow routines, like those using I/O operations. For some reason, I/O operations still need around one microsecond each, so these will slow down the routine enough to make sure there will be no overflow in the loop-counters.

A friend of mine just uses IN instructions from some silly address to get reasonably accurate timingloops, assuming that 1 IN operation is about 1 microsecond. Bit it could well lead to trouble on modern PCI hardware.

All in all, for most delay-routines, the dumb waiting function is by far the best since it is the most reliable and accurate to less than a microsecond. But if you need this many digits, use compensated software, that takes into account the time to read the timers twice -- because you need to keep in mind that also this routine relies heavily on I/O instructions, so it is not infinitely fast!

In a future article I will describe how to use the RTC chip for generating timing signals and how to use it via the Programmable Interrupt Controller in automatic mode. That article will be pure ASM again, so don't be worried about this detour into Modula.