language-Modula2-0.1: examples/Modula-2_Libraries/PMOS/sources/general/timer.mod
IMPLEMENTATION MODULE Timer;
(* disable # debug(vid=>off) *)
(********************************************************)
(* *)
(* Clock tick handler *)
(* *)
(* Author: P. Moylan *)
(* Last edited: 17 March 1995 *)
(* Description: *)
(* This module contains the clock interrupt *)
(* handler. It checks whether the current task *)
(* has used its time quota, also keeps track of *)
(* delayed tasks and timeouts. *)
(* *)
(* Status: OK *)
(* *)
(********************************************************)
FROM LowLevel IMPORT
(*<ChainTimerInt*)
(* proc *) INCV, MakePointer,
(*>*)
(* proc *) LowByte, HighByte, OutByte;
FROM MiscPMOS IMPORT
(* proc *) EnterCriticalSection, LeaveCriticalSection;
FROM Semaphores IMPORT
(*<ChainTimerInt*)
(* proc *) CreateSemaphore, Wait, Signal,
(*>*)
(* proc *) TimedWaitT;
FROM TaskControl IMPORT
(*<ChainTimerInt*)
(* proc *) CreateTask, NotUsingFloatingPoint,
(*>*)
(* proc *) CreateInterruptTask, KillInterruptTask, WaitForInterrupt,
Delay, CheckSleepers, TimeSliceCheck;
FROM TerminationControl IMPORT
(* proc *) SetTerminationProcedure;
(************************************************************************)
CONST MillisecondsPerTick = 20;
(* It is convenient to choose milliseconds as the system-wide unit *)
(* for things like time delays (or, at least, for time delays *)
(* controlled by this module), on the grounds that a resolution *)
(* finer than one millisecond could make the handling of timer *)
(* interrupts a major source of system overhead. *)
(* Hardware limitations restrict the value of MillisecondsPerTick *)
(* to be in the range [1..109]. We could in principle go as low as *)
(* 1.7 microseconds per tick by using real arithmetic, but that *)
(* would be fooling ourselves - the timer can go that fast, but the *)
(* processor would not keep up. *)
CONST channel0 = 040H; TimerControlPort = 043H;
(* I/O port definitions for the 8254 timer chip. *)
(* The following variables are used only in the case where we want *)
(* to enable the timer interrupt handler that was active before *)
(* this program started running. There's some overhead in doing *)
(* this, but in some environments it's necessary in order to avoid *)
(* killing background stuff. *)
(*<ChainTimerInt*)
(*# save, call(interrupt=>on, reg_param=>(), same_ds=>off, near_call=>off) *)
(*# data(near_ptr => off) *)
TYPE IntProc = PROCEDURE();
IntProcPointer = POINTER TO IntProc;
VAR OriginalHandler: IntProc;
(*# restore *)
VAR
ClockCount, CountPerInterrupt: CARDINAL;
CounterWrap: Semaphore;
(*>*)
(************************************************************************)
(* "PUT-ME-TO-SLEEP" PROCEDURE *)
(************************************************************************)
PROCEDURE Sleep (milliseconds: CARDINAL);
(* Puts the caller to sleep for approximately the given number of *)
(* milliseconds. The time is not guaranteed to be precise, because *)
(* (a) after the specified time has expired, the caller is made *)
(* ready, but will not run immediately unless it has a higher *)
(* priority than the task which is running at that time, and (b) we *)
(* do not necessarily run the hardware timer with millisecond *)
(* resolution anyway. High resolution just adds to the system *)
(* overhead created by the timer interrupts. For an application *)
(* which genuinely needs high-precision delays, it makes more sense *)
(* to have a separate hardware timer dedicated just to that job. *)
BEGIN
Delay (milliseconds DIV MillisecondsPerTick);
END Sleep;
(************************************************************************)
(* SEMAPHORE WAIT WITH TIMEOUT *)
(************************************************************************)
PROCEDURE TimedWait (VAR (*INOUT*) s: Semaphore; TimeLimit: INTEGER;
VAR (*OUT*) TimedOut: BOOLEAN);
(* Like a semaphore Wait, except that it returns with TimedOut TRUE *)
(* if the corresponding Signal does not occur within TimeLimit *)
(* milliseconds. *)
BEGIN
TimedWaitT (s,
(TimeLimit + MillisecondsPerTick DIV 2) DIV MillisecondsPerTick,
TimedOut);
END TimedWait;
(************************************************************************)
(* THE INTERRUPT TASK *)
(************************************************************************)
PROCEDURE ClockInterruptTask;
(* Activated periodically by the hardware clock. In between ticks, *)
(* we become dormant via a call to WaitForInterrupt. This task *)
(* has two functions: *)
(* 1. Check whether a sleeping task needs to be woken up, and wake *)
(* it up if so. *)
(* 2. Check whether the running task has used its time quota, and *)
(* if so deal with that situation. *)
(* To spread the load on the system, we perform the above functions *)
(* on alternate clock ticks. *)
BEGIN
LOOP (*FOREVER*)
(* On the first interrupt, check the time allotment of *)
(* the interrupted task. (This does nothing unless *)
(* timeslicing is enabled in module TaskControl.) *)
WaitForInterrupt;
TimeSliceCheck;
(*<ChainTimerInt*)
IF INCV (ClockCount, CountPerInterrupt) THEN
Signal (CounterWrap);
END (*IF*);
(*>*)
(* On the next interrupt, check for sleeping tasks. *)
WaitForInterrupt;
(*<ChainTimerInt*)
IF INCV (ClockCount, CountPerInterrupt) THEN
(* Important note: this Signal must come BEFORE the *)
(* call to CheckSleepers, because of complications *)
(* related to the way the kernel handles time-outs. *)
Signal (CounterWrap);
END (*IF*);
(*>*)
CheckSleepers;
END (*LOOP*);
END ClockInterruptTask;
(************************************************************************)
(* EXECUTING THE ORIGINAL TIMER INTERRUPT ROUTINE *)
(************************************************************************)
(*<ChainTimerInt*)
PROCEDURE LookAfterOriginalInterruptRoutine;
(* This is a separate task which calls the original interrupt *)
(* routine (i.e. the one that was active before this module took *)
(* over) each time the timer count wraps around. *)
(* You're probably wondering why we use this roundabout method of *)
(* calling the routine. The answer is that there's a conflict *)
(* between the PMOS design conventions and the BIOS approach. *)
(* PMOS requires that interrupt tasks be non-interruptible; but *)
(* BIOS interrupt handlers often re-enable interrupts. Thus, we *)
(* cannot safely execute a BIOS routine from inside an interrupt *)
(* task. *)
BEGIN
NotUsingFloatingPoint ();
LOOP
Wait (CounterWrap);
OriginalHandler;
END (*LOOP*);
END LookAfterOriginalInterruptRoutine;
(*>*)
(************************************************************************)
(* CLEANUP ON PROGRAM TERMINATION *)
(************************************************************************)
PROCEDURE SetCount (count: CARDINAL);
(* Set up timer 0 to generate a square wave of period proportional *)
(* to parameter 'count'. Note: There are three timers on the timer *)
(* chip, but only timer 0 is available for our present purposes; *)
(* the other two are hardwired to do sound generation and dynamic *)
(* RAM refresh. *)
BEGIN
OutByte (TimerControlPort, 036H);
OutByte (channel0, LowByte(count));
OutByte (channel0, HighByte(count));
END SetCount;
(************************************************************************)
PROCEDURE Cleanup;
(* Restores the timer 0 frequency to that used by MS-DOS, and *)
(* de-installs the interrupt handler. *)
VAR savedPSW: CARDINAL;
BEGIN
savedPSW := EnterCriticalSection();
SetCount (0);
KillInterruptTask (8);
LeaveCriticalSection (savedPSW);
END Cleanup;
(************************************************************************)
(* INITIALISATION *)
(************************************************************************)
PROCEDURE InitialiseClock;
(* Performs the appropriate hardware initialisation to (a) set up *)
(* the hardware timer to interrupt at the desired rate, and (b) set *)
(* up the interrupt vector to point to the interrupt routine. *)
VAR savedPSW: CARDINAL;
(*<ChainTimerInt*) p: IntProcPointer; (*>*)
BEGIN
savedPSW := EnterCriticalSection();
(* The input frequency to the hardware timer is 1.19318 MHz, *)
(* and the actual interrupt frequency is this frequency divided *)
(* by variable "count". There is an extra factor of 2 caused *)
(* by the fact that our interrupt task effectively divides *)
(* the interrupt frequency by 2. The end result is the *)
(* following formula: *)
(*<~ChainTimerInt
SetCount ((59659 * MillisecondsPerTick + 50) DIV 100);
>*)
(*<ChainTimerInt*)
(* In the case where we're chaining the timer interrupt, we *)
(* have to initialise some extra variables and create a new *)
(* task to handle the job. *)
CountPerInterrupt := (59659 * MillisecondsPerTick + 50) DIV 100;
SetCount (CountPerInterrupt);
ClockCount := 0;
CreateSemaphore (CounterWrap, 0);
p := MakePointer (0, 8*4);
OriginalHandler := p^;
(* The priority assigned in the next call may need to be varied *)
(* depending on the application. With a Microsoft mouse driver *)
(* it is essential that the priority be >1; otherwise the mouse *)
(* driver crashes the entire system. With some communications *)
(* drivers we might want to make the priority even higher. *)
CreateTask (LookAfterOriginalInterruptRoutine, 2, "Old timer int");
(*>*)
(* Connect the interrupt routine to the interrupt source. *)
CreateInterruptTask (8, ClockInterruptTask, "Heartbeat tick");
LeaveCriticalSection (savedPSW);
END InitialiseClock;
(************************************************************************)
BEGIN
SetTerminationProcedure (Cleanup);
InitialiseClock;
END Timer.