1 (* TimerHandler.mod provides a simple timer handler for the Executive.
3 Copyright (C) 2002-2024 Free Software Foundation, Inc.
4 Contributed by Gaius Mulley <gaius.mulley@southwales.ac.uk>.
6 This file is part of GNU Modula-2.
8 GNU Modula-2 is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 3, or (at your option)
13 GNU Modula-2 is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
18 Under Section 7 of GPL version 3, you are granted additional
19 permissions described in the GCC Runtime Library Exception, version
20 3.1, as published by the Free Software Foundation.
22 You should have received a copy of the GNU General Public License and
23 a copy of the GCC Runtime Library Exception along with this program;
24 see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
25 <http://www.gnu.org/licenses/>. *)
27 IMPLEMENTATION MODULE TimerHandler
[MAX(PROTECTION
)] ;
30 FROM COROUTINES
IMPORT PROTECTION
;
31 FROM SysStorage
IMPORT ALLOCATE
;
32 FROM NumberIO
IMPORT CardToStr
;
33 FROM Debug
IMPORT Halt
, DebugString
;
34 FROM KeyBoardLEDs
IMPORT SwitchScroll
;
35 FROM RTint
IMPORT ReArmTimeVector
, GetTimeVector
, InitTimeVector
;
36 FROM Executive
IMPORT DESCRIPTOR
, Suspend
, Resume
, GetCurrentProcess
,
37 WaitForIO
, InitProcess
, RotateRunQueue
,
41 MaxQuantum
= 4 ; (* Maximum ticks a process may consume *)
42 (* before being rescheduled. *)
43 BaseTicks
= 1000000 ; (* Max resolution of clock ticks per sec *)
44 TimerStackSize
= 100000H
; (* Reasonable sized stack for a process *)
45 Debugging
= FALSE ; (* Do you want lots of debugging info? *)
46 EnableLED
= FALSE ; (* Should the scroll LED be pulsed? *)
49 EVENT
= POINTER TO RECORD
52 Process
: DESCRIPTOR
;
53 NoOfTicks
: CARDINAL ;
54 WasCancelled
: BOOLEAN ;
57 (* the queue types are either:
59 active queue which has a list of outstanding events
60 dead queue which is essentially the free list
61 solo which is no queue and the event is in limbo
64 QueueType
= (active
, dead
, solo
) ;
72 TotalTicks
: CARDINAL ; (* System up time tick count *)
73 CurrentQuanta
: CARDINAL ; (* Currentprocess time quanta allowance *)
74 ActiveQueue
, (* Queue of outstanding timer requests *)
75 DeadQueue
: EVENT
; (* Free list of events. *)
79 GetTicks - returns the number of ticks since boottime.
82 PROCEDURE GetTicks () : CARDINAL ;
84 ToOldState
: PROTECTION
;
85 CopyOfTicks
: CARDINAL ;
87 (* ToOldState := TurnInterrupts(MAX(PROTECTION)) ; (* disable interrupts *) *)
88 CopyOfTicks
:= TotalTicks
;
89 (* ToOldState := TurnInterrupts(ToOldState) ; (* restore interrupts *) *)
95 Sleep - suspends the current process for a time, t.
96 The time is measured in ticks.
99 PROCEDURE Sleep (t
: CARDINAL) ;
101 ToOldState
: PROTECTION
;
104 (* ToOldState := TurnInterrupts(MAX(PROTECTION)) ; (* disable interrupts *) *)
109 (* ToOldState := TurnInterrupts(ToOldState) (* restore interrupts *) *)
114 More lower system calls to the timer procedures follow,
115 they are necessary to allow handling multiple events.
120 ArmEvent - initializes an event, e, to occur at time, t.
121 The time, t, is measured in ticks.
122 The event is NOT placed onto the event queue.
125 PROCEDURE ArmEvent (t
: CARDINAL) : EVENT
;
128 ToOldState
: PROTECTION
;
131 (* ToOldState := TurnInterrupts(MAX(PROTECTION)) ; (* disable interrupts *) *)
134 (* your code needs to go here *)
135 WITH e^
DO (* remove for student *)
136 InitQueue(EventQ
) ; (* not on a queue yet *) (* remove for student *)
137 WhichQ
:= solo
; (* and set the queue state accordingly *) (* remove for student *)
138 Process
:= NIL ; (* no process waiting event yet *) (* remove for student *)
139 NoOfTicks
:= t
; (* absolute number of ticks *) (* remove for student *)
140 WasCancelled
:= FALSE ; (* has not been cancelled *) (* remove for student *)
141 END ; (* remove for student *)
143 (* ToOldState := TurnInterrupts(ToOldState) ; (* restore interrupts *) *)
149 WaitOn - places event, e, onto the event queue and then the calling
150 process suspends. It is resumed up by either the event
151 expiring or the event, e, being cancelled.
152 TRUE is returned if the event was cancelled
153 FALSE is returned if the event expires.
154 The event, e, is always assigned to NIL when the function
158 PROCEDURE WaitOn (VAR e
: EVENT
) : BOOLEAN ;
160 ToOldState
: PROTECTION
;
161 Cancelled
: BOOLEAN ;
163 (* ToOldState := TurnInterrupts(MAX(PROTECTION)) ; (* disable interrupts *) *)
166 Halt ('event should never be NIL',
167 __FILE__
, __FUNCTION__
, __LINE__
)
170 (* we will just check to see whether someone has cancelled this *)
171 (* event before it ever got to the queue... *)
174 (* right so it wasn't cancelled. Lets place it on the queue and *)
176 Process
:= GetCurrentProcess() ; (* so we know who is waiting *)
177 OnActiveQueue(e
) ; (* add to the queue and then *)
181 DisplayActive
; (* debugging *)
184 Suspend (* wait for Resume (we sleep) *)
186 (* At this point we have either been cancelled or not. We must *)
187 (* check the event again as we might have been sleeping (Suspend) *)
188 Cancelled
:= WasCancelled
191 OnDeadQueue(e
) ; (* now it is safe to throw this event away *)
193 (* ToOldState := TurnInterrupts(ToOldState) ; (* restore interrupts *) *)
199 Cancel - cancels the event, e, on the event queue and makes
200 the appropriate process runnable again.
201 TRUE is returned if the event was cancelled and
202 FALSE is returned is the event was not found or
203 no process was waiting on this event.
206 PROCEDURE Cancel (e
: EVENT
) : BOOLEAN ;
208 ToOldState
: PROTECTION
;
209 Cancelled
: BOOLEAN ;
210 Private
: DESCRIPTOR
;
212 (* ToOldState := TurnInterrupts(MAX(PROTECTION)) ; (* disable interrupts *) *)
213 IF IsOnActiveQueue(e
)
216 Cancelled
:= NOT WasCancelled
;
219 Halt ('inconsistancy event has been cancelled and it is on queue',
220 __FILE__
, __FUNCTION__
, __LINE__
)
223 WasCancelled
:= TRUE ;
224 IF Process#
NIL (* double check that it has not *)
225 (* already been cancelled *)
227 Private
:= Process
; (* we use our own Private variable *)
228 Process
:= NIL ; (* as we need to set Process to NIL *)
229 Process
:= Resume(Private
) (* before we Resume. Otherwise *)
230 (* there is the possibility that it *)
231 (* might be reused before we := NIL *)
232 (* (because when we touch Resume *)
233 (* another process could run and..) *)
239 (* ToOldState := TurnInterrupts(ToOldState) ; (* restore interrupts *) *)
245 ReArmEvent - removes an event, e, from the event queue. A new time
246 is given to this event and it is then re-inserted onto the
247 event queue in the correct place.
248 TRUE is returned if this occurred
249 FALSE is returned if the event was not found.
252 PROCEDURE ReArmEvent (e
: EVENT
; t
: CARDINAL) : BOOLEAN ;
254 ToOldState
: PROTECTION
;
257 (* ToOldState := TurnInterrupts(MAX(PROTECTION)) ; (* disable interrupts *) *)
262 ELSIF IsOnActiveQueue(e
) OR IsOnSoloQueue(e
)
265 OnSoloQueue(e
) ; (* remove from queue *)
266 NoOfTicks
:= t
; (* give it a new time *)
267 OnActiveQueue(e
) (* back on queue *)
269 Halt ('ReArm should not be asked to ReArm a dead event',
270 __FILE__
, __FUNCTION__
, __LINE__
)
273 (* ToOldState := TurnInterrupts(ToOldState) ; (* restore interrupts *) *)
279 StartClock - ticks is milli seconds.
282 PROCEDURE StartClock (vec
: CARDINAL; ticks
: CARDINAL) ;
284 ReArmTimeVector (vec
, ticks
MOD BaseTicks
, ticks
DIV BaseTicks
)
289 LoadClock - returns the number of milli seconds.
292 PROCEDURE LoadClock (vec
: CARDINAL) : CARDINAL ;
294 micro
, secs
: CARDINAL ;
296 GetTimeVector (vec
, micro
, secs
) ;
297 RETURN secs
* BaseTicks
+ micro
302 Timer - is a process which serves the clock interrupt.
303 Its function is fourfold:
305 (i) to maintain the timer event queue
306 (ii) to give some fairness to processes via round robin scheduling
307 (iii) to keep a count of the total ticks so far (time of day)
308 (iv) provide a heartbeat sign of life via the scroll lock LED
313 CurrentCount
: CARDINAL ;
314 ToOldState
: PROTECTION
;
315 ScrollLED
: BOOLEAN ;
316 TimerIntNo
: CARDINAL ;
319 (* ToOldState := TurnInterrupts(MAX(PROTECTION)) ; *)
321 TimerIntNo
:= InitTimeVector ((BaseTicks
DIV TicksPerSecond
) MOD BaseTicks
,
322 (BaseTicks
DIV TicksPerSecond
) DIV BaseTicks
,
325 WaitForIO (TimerIntNo
) ;
327 (* Get current clock count *)
328 CurrentCount
:= (* LoadClock(TimerIntNo) ; *) 0 ;
329 (* Now compenstate for lost ticks *)
330 StartClock (TimerIntNo
, CurrentCount
+ (BaseTicks
DIV TicksPerSecond
)) ;
332 INC (TotalTicks
) ; (* (iii) *)
335 (* now pulse scroll LED *)
336 IF (TotalTicks
MOD TicksPerSecond
) = 0
338 ScrollLED
:= NOT ScrollLED
;
339 (* r := printf("<scroll %d>", TotalTicks); *)
340 SwitchScroll(ScrollLED
) (* (iv) *)
343 IF (TotalTicks
MOD MaxQuantum
) = 0
345 RotateRunQueue (* (ii) *)
348 CheckActiveQueue (* (i) *)
354 CheckActiveQueue - purpose is:
356 (i) to remove all events which have expired
357 (ii) resume all processes waiting on these events
358 (iii) decrement the first event with a non zero NoOfTicks
361 PROCEDURE CheckActiveQueue
;
364 Private
: DESCRIPTOR
;
368 DebugString('inside CheckActiveQueue\n') ;
371 WHILE (ActiveQueue#
NIL) AND (ActiveQueue^.NoOfTicks
=0) DO (* (i) *)
374 (* note we do not put it onto the dead queue. The process
375 waiting for the event will place, e, onto the dead queue *)
377 IF (NOT WasCancelled
) AND (Process#
NIL)
379 Private
:= Process
; (* we use our own Private variable *)
380 Process
:= NIL ; (* as we might context switch in *)
381 Process
:= Resume(Private
) ; (* resume. (ii) *)
391 DEC(ActiveQueue^.NoOfTicks
) (* (iii) *)
395 DebugString('after CheckActiveQueue\n') ;
398 END CheckActiveQueue
;
402 CreateSolo - create a new event. It does this by either getting an event from
403 the dead queue or (if the dead queue is empty) an event is created
407 PROCEDURE CreateSolo () : EVENT
;
416 SubFrom(DeadQueue
, e
)
424 RemoveFromDead - removes event, e, from the dead queue.
427 PROCEDURE RemoveFromDead (e
: EVENT
) ;
429 SubFrom(DeadQueue
, e
)
434 OnDeadQueue - places an event onto the dead queue.
437 PROCEDURE OnDeadQueue (e
: EVENT
) ;
441 OnSoloQueue(e
) ; (* put on solo queue first *)
442 AddTo(DeadQueue
, e
) ; (* now safe to put on dead queue *)
449 OnSoloQueue - places an event onto the solo queue.
452 PROCEDURE OnSoloQueue (e
: EVENT
) ;
456 IF IsOnActiveQueue(e
)
459 ELSIF IsOnDeadQueue(e
)
469 OnActiveQueue - places an event onto the active queue.
472 PROCEDURE OnActiveQueue (e
: EVENT
) ;
478 Halt ('illegal state change',
479 __FILE__
, __FUNCTION__
, __LINE__
)
480 ELSIF IsOnSoloQueue(e
)
482 RelativeAddToActive(e
) ;
490 IsOnSoloQueue - returns TRUE if event, e, is on the solo queue.
493 PROCEDURE IsOnSoloQueue (e
: EVENT
) : BOOLEAN ;
495 RETURN( (e#
NIL) AND (e^.WhichQ
=solo
) )
500 IsOnDeadQueue - returns TRUE if event, e, is on the dead queue.
503 PROCEDURE IsOnDeadQueue (e
: EVENT
) : BOOLEAN ;
505 RETURN( (e#
NIL) AND (e^.WhichQ
=dead
) )
510 IsOnActiveQueue - returns TRUE if event, e, is on the active queue.
513 PROCEDURE IsOnActiveQueue (e
: EVENT
) : BOOLEAN ;
515 RETURN( (e#
NIL) AND (e^.WhichQ
=active
) )
516 END IsOnActiveQueue
;
520 RemoveFromActive - removes an event, e, from the active queue.
523 PROCEDURE RemoveFromActive (e
: EVENT
) ;
527 SubFrom(ActiveQueue
, e
) ;
528 (* providing that the ActiveQueue is non empty we need to
529 modify first event ticks as we have removed the first event, e. *)
532 INC(ActiveQueue^.NoOfTicks
, e^.NoOfTicks
)
535 (* providing that event, e, is not the last event on the list then
536 update the next event by the time of, e. *)
537 IF e^.EventQ.Right#ActiveQueue
539 INC(e^.EventQ.Right^.NoOfTicks
, e^.NoOfTicks
)
541 SubFrom(ActiveQueue
, e
)
543 END RemoveFromActive
;
547 InsertBefore - insert an event, new, on a circular event queue BEFORE
551 PROCEDURE InsertBefore (VAR Head
: EVENT
; pos
, new
: EVENT
) ;
557 new^.EventQ.Right
:= new
;
558 new^.EventQ.Left
:= new
561 (* insert before the first element on the queue *)
562 new^.EventQ.Right
:= pos
;
563 new^.EventQ.Left
:= pos^.EventQ.Left
;
564 pos^.EventQ.Left^.EventQ.Right
:= new
;
565 pos^.EventQ.Left
:= new
;
568 (* insert before any other element *)
569 new^.EventQ.Right
:= pos
;
570 new^.EventQ.Left
:= pos^.EventQ.Left
;
571 pos^.EventQ.Left^.EventQ.Right
:= new
;
572 pos^.EventQ.Left
:= new
578 InsertAfter - place an event, new, AFTER the event pos on any circular event queue.
581 PROCEDURE InsertAfter (pos
, new
: EVENT
) ;
583 new^.EventQ.Right
:= pos^.EventQ.Right
;
584 new^.EventQ.Left
:= pos
;
585 pos^.EventQ.Right^.EventQ.Left
:= new
;
586 pos^.EventQ.Right
:= new
591 RelativeAddToActive - the active event queue is an ordered queue of
592 relative time events.
593 The event, e, is inserted at the appropriate
594 position in the queue. The event, e, enters
595 this routine with an absolute NoOfTicks field which
596 is then used to work out the relative position
597 of the event. After the position is found then
598 the absolute NoOfTicks field is altered to a
599 relative value and inserted on the queue.
602 PROCEDURE RelativeAddToActive (e
: EVENT
) ;
609 (* simple as the queue is empty (relative=absolute) *)
610 InsertBefore (ActiveQueue
, ActiveQueue
, e
)
612 (* at the end of the while loop sum will contain the total of all
613 events up to but not including, t.
614 If the value of sum is < e^.NoOfTicks then e must be placed at the end
615 >= e^.NoOfTicks then e needs to be placed in the middle
618 sum
:= ActiveQueue^.NoOfTicks
;
619 t
:= ActiveQueue^.EventQ.Right
; (* second event *)
620 WHILE (sum
< e^.NoOfTicks
) AND (t # ActiveQueue
) DO
621 INC (sum
, t^.NoOfTicks
) ;
624 IF sum
< e^.NoOfTicks
626 (* e will occur after all the current ActiveQueue has expired therefore
627 we must add it to the end of the ActiveQueue. *)
628 DEC (e^.NoOfTicks
, sum
) ;
629 InsertAfter (ActiveQueue^.EventQ.Left
, e
)
631 (* as sum >= e^.NoOfTicks we know that e is scheduled to occur
632 in the middle of the queue but before t^.Left
634 DEC (e^.NoOfTicks
, sum
-t^.EventQ.Left^.NoOfTicks
) ;
635 InsertBefore (ActiveQueue
, t^.EventQ.Left
, e
)
637 (* the first event after e must have its relative NoOfTicks altered *)
638 IF e^.EventQ.Right # ActiveQueue
640 DEC (e^.EventQ.Right^.NoOfTicks
, e^.NoOfTicks
)
643 END RelativeAddToActive
;
647 AddTo - adds an event to a specified queue.
650 PROCEDURE AddTo (VAR Head
: EVENT
; e
: EVENT
) ;
655 e^.EventQ.Left
:= e
;
658 e^.EventQ.Right
:= Head
;
659 e^.EventQ.Left
:= Head^.EventQ.Left
;
660 Head^.EventQ.Left^.EventQ.Right
:= e
;
661 Head^.EventQ.Left
:= e
667 SubFrom - removes an event from a queue.
670 PROCEDURE SubFrom (VAR Head
: EVENT
; e
: EVENT
) ;
672 IF (e^.EventQ.Left
= Head
) AND (e
= Head
)
678 Head
:= Head^.EventQ.Right
680 e^.EventQ.Left^.EventQ.Right
:= e^.EventQ.Right
;
681 e^.EventQ.Right^.EventQ.Left
:= e^.EventQ.Left
687 DisplayActive - display the active queue.
690 PROCEDURE DisplayActive
;
706 DisplayEvent - display a single event, e.
709 PROCEDURE DisplayEvent (e
: EVENT
) ;
711 a
: ARRAY [0.
.20] OF CHAR ;
714 CardToStr(NoOfTicks
, 6, a
) ;
716 DebugString(' process (') ;
719 DebugString('is NIL') ;
726 DebugString(' has been cancelled')
737 PROCEDURE InitQueue (VAR q
: Queue
) ;
745 Init - starts the timer process and initializes some queues.
756 d
:= Resume(InitProcess(Timer
, TimerStackSize
, 'Timer'))