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 (**) (* proc *) LowByte, HighByte, OutByte; FROM MiscPMOS IMPORT (* proc *) EnterCriticalSection, LeaveCriticalSection; FROM Semaphores IMPORT (**) (* proc *) TimedWaitT; FROM TaskControl IMPORT (**) (* 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. *) (*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; (**) (* On the next interrupt, check for sleeping tasks. *) WaitForInterrupt; (**) CheckSleepers; END (*LOOP*); END ClockInterruptTask; (************************************************************************) (* EXECUTING THE ORIGINAL TIMER INTERRUPT ROUTINE *) (************************************************************************) (**) (************************************************************************) (* 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; (**) 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); >*) (*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.