(* Executive.mod provides a simple multitasking executive.

Copyright (C) 2002-2025 Free Software Foundation, Inc.
Contributed by Gaius Mulley <gaius.mulley@southwales.ac.uk>.

This file is part of GNU Modula-2.

GNU Modula-2 is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.

GNU Modula-2 is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

Under Section 7 of GPL version 3, you are granted additional
permissions described in the GCC Runtime Library Exception, version
3.1, as published by the Free Software Foundation.

You should have received a copy of the GNU General Public License and
a copy of the GCC Runtime Library Exception along with this program;
see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
<http://www.gnu.org/licenses/>.  *)

IMPLEMENTATION MODULE Executive[MAX(PROTECTION)] ;

FROM SYSTEM IMPORT ADDRESS, PROCESS, LISTEN, ADR,
                   NEWPROCESS, TRANSFER, IOTRANSFER, ListenLoop,
                   TurnInterrupts ;

FROM COROUTINES IMPORT PROTECTION ;
FROM SysStorage IMPORT ALLOCATE, DEALLOCATE ;
FROM StrLib IMPORT StrCopy ;
FROM StrLib IMPORT StrLen ;
FROM NumberIO IMPORT CardToStr ;
FROM Debug IMPORT DebugString, Halt ;


(* IMPORT gdb ; *)


CONST
   MaxCharsInName = 15 ;
   IdleStackSize  = 16 * 1024 * 1024 ;

TYPE
   SEMAPHORE = POINTER TO Semaphore ;         (* defines dijkstra's semaphores *)
   Semaphore = RECORD
                  Value  : CARDINAL ;         (* semaphore value               *)
                  SemName: EntityName ;       (* semaphore name for debugging  *)
                  Who    : DESCRIPTOR ;       (* queue of waiting processes    *)
                  ExistsQ: SemQueue ;         (* list of existing semaphores   *)
               END ;

   DESCRIPTOR= POINTER TO Descriptor ;        (* handle onto a process         *)
   Descriptor= RECORD
                  Volatiles  : PROCESS ;      (* process volatile environment  *)
                  ReadyQ     : DesQueue ;     (* queue of ready processes      *)
                  ExistsQ    : DesQueue ;     (* queue of existing processes   *)
                  SemaphoreQ : DesQueue ;     (* queue of waiting processes    *)
                  Which      : SEMAPHORE ;    (* which semaphore are we waiting*)
                  RunName    : EntityName ;   (* process name for debugging    *)
                  Status     : State ;        (* state of process              *)
                  RunPriority: Priority ;     (* runtime priority of process   *)
                  Size       : CARDINAL ;     (* Maximum stack size            *)
                  Start      : ADDRESS ;      (* Stack start                   *)
                  Debugged   : BOOLEAN ;      (* Does user want to debug a     *)
                                              (* deadlocked process?           *)
               END ;

   DesQueue  = RECORD
                  Right,
                  Left : DESCRIPTOR ;
               END ;

   SemQueue  = RECORD
                  Right,
                  Left : SEMAPHORE ;
               END ;

   EntityName= ARRAY [0..MaxCharsInName] OF CHAR ;

   Priority  = (idle, lo, hi) ;               (* process run priority          *)

   State     = (Runnable, Suspended, WaitOnSem, WaitOnInt) ;

VAR
   ExistsQueue   : DESCRIPTOR ;               (* List of existing processes    *)
   RunQueue      : ARRAY Priority OF DESCRIPTOR ;
                                              (* List of runnable processes    *)
   CurrentProcess: DESCRIPTOR ;
   AllSemaphores : SEMAPHORE ;                (* List of all semaphores        *)
   GarbageItem   : DESCRIPTOR ;               (* Descriptor destined to free   *)


(*
   Assert -
*)

PROCEDURE Assert (c: BOOLEAN; file: ARRAY OF CHAR; line: CARDINAL;
                  function: ARRAY OF CHAR) ;
BEGIN
   IF NOT c
   THEN
      Ps ;
      Halt ('assert failed', file, function, line)
   END
END Assert ;


(*
   InitProcess - initializes a process which is held in the suspended
                 state. When the process is resumed it will start executing
                 procedure, p. The process has a maximum stack size of,
                 StackSize, bytes and its textual name is, Name.
                 The StackSize should be at least 5000 bytes.
*)

PROCEDURE InitProcess (p: PROC;
                       StackSize: CARDINAL;
                       Name: ARRAY OF CHAR) : DESCRIPTOR ;
VAR
   d         : DESCRIPTOR ;
   ToOldState: PROTECTION ;
   db        : ARRAY [0..80] OF CHAR ;
BEGIN
(* ToOldState := TurnInterrupts(MAX(PROTECTION)) ;                (* disable interrupts *) *)
   NEW(d) ;
   WITH d^ DO
      Size        := StackSize ;
                                 (* allocate space for this processes stack *)
      ALLOCATE(Start, StackSize) ;
      NEWPROCESS(p, Start, StackSize, Volatiles) ;  (* create volatiles     *)
      InitQueue(ReadyQ) ;        (* not on the ready queue as suspended     *)
      AddToExists(d) ;           (* add process to the exists queue         *)
      InitQueue(SemaphoreQ) ;    (* not on a semaphore queue yet            *)
      Which       := NIL ;       (* not on a semaphore queue yet            *)
      StrCopy(Name, RunName) ;   (* copy name into descriptor for debugging *)
      Status      := Suspended ; (* this process will be suspended          *)
      RunPriority := lo ;        (* all processes start off at lo priority  *)
      Debugged    := FALSE ;     (* no need to debug deadlock yet!          *)
   END ;
(* ToOldState := TurnInterrupts(ToOldState) ;         (* restore interrupts *) *)
   RETURN( d )                   (* and return a descriptor to the caller   *)
END InitProcess ;


(*
   KillProcess - kills the current process. Notice that if InitProcess
                 is called again, it might reuse the DESCRIPTOR of the
                 killed process. It is the responsibility of the caller
                 to ensure all other processes understand this process
                 is different.
*)

PROCEDURE KillProcess ;
VAR
   ToOldState: PROTECTION ;
BEGIN
(* ToOldState := TurnInterrupts(MAX(PROTECTION)) ;    (* disable interrupts *) *)
   SubFromReady(CurrentProcess) ;
   SubFromExists(ExistsQueue, CurrentProcess) ;
   GarbageItem := CurrentProcess ;
   Reschedule ;
(* ToOldState := TurnInterrupts(ToOldState)         (* restore interrupts *) *)
END KillProcess ;


(*
   Resume - resumes a suspended process. If all is successful then the process, p,
            is returned. If it fails then NIL is returned.
*)

PROCEDURE Resume (d: DESCRIPTOR) : DESCRIPTOR ;
VAR
   ToOldState: PROTECTION ;
BEGIN
(* ToOldState := TurnInterrupts(MAX(PROTECTION)) ;                (* disable interrupts *) *)
   WITH d^ DO
      IF Status=Suspended
      THEN
         (* legal state transition *)
         Status := Runnable ;                         (* change status      *)
         AddToReady(d) ;                              (* add to run queue   *)
         RunQueue[RunPriority] := d ;                 (* make d at top of q *)
         Reschedule (* check whether this process has a higher run priority *)
      ELSE
         (* we are trying to Resume a process which is *)
         Halt ('trying to resume a process which is not suspended',
               __FILE__, __FUNCTION__, __LINE__) ;
         RETURN( NIL )        (* not held in a Suspended state - error      *)
      END
   END ;
(* ToOldState := TurnInterrupts(ToOldState) ;         (* restore interrupts *) *)
   RETURN( d )
END Resume ;


(*
   Suspend - suspend the calling process.
             The process can only continue running if another process
             Resumes it.
*)

PROCEDURE Suspend ;
VAR
   ToOldState: PROTECTION ;
BEGIN
(* ToOldState := TurnInterrupts(MAX(PROTECTION)) ;                (* disable interrupts *) *)
   WITH CurrentProcess^ DO
      Status := Suspended
   END ;
   SubFromReady(CurrentProcess) ;
   Reschedule ;
(* ToOldState := TurnInterrupts(ToOldState)           (* restore interrupts *) *)
END Suspend ;


(*
   InitSemaphore - creates a semaphore whose initial value is, v, and
                   whose name is, Name.
*)

PROCEDURE InitSemaphore (v: CARDINAL; Name: ARRAY OF CHAR) : SEMAPHORE ;
VAR
   s         : SEMAPHORE ;
   ToOldState: PROTECTION ;
BEGIN
(* ToOldState := TurnInterrupts(MAX(PROTECTION)) ;                (* disable interrupts *) *)
   NEW(s) ;
   WITH s^ DO
      Value := v ;                  (* initial value of semaphore           *)
      StrCopy(Name, SemName) ;      (* save the name for future debugging   *)
      Who := NIL ;                  (* no one waiting on this semaphore yet *)
      AddToSemaphoreExists(s) ;     (* add semaphore to exists list         *)
   END ;
(* ToOldState := TurnInterrupts(ToOldState) ;         (* restore interrupts *) *)
   RETURN( s )
END InitSemaphore ;


(*
   Wait - performs dijkstra's P operation on a semaphore.
          A process which calls this procedure will
          wait until the value of the semaphore is > 0
          and then it will decrement this value.
*)

PROCEDURE Wait (s: SEMAPHORE) ;
VAR
   ToOldState: PROTECTION ;
BEGIN
(* ToOldState := TurnInterrupts(MAX(PROTECTION)) ;                (* disable interrupts *) *)
   WITH s^ DO
      IF Value>0
      THEN
         DEC( Value )
      ELSE
         SubFromReady(CurrentProcess) ;               (* remove from run q  *)
         IF Who=CurrentProcess
         THEN
            Ps ;
            Halt ('we are already on sem',
                  __FILE__, __FUNCTION__, __LINE__)
         END ;
         AddToSemaphore(Who, CurrentProcess) ;        (* add to semaphore q *)
         CurrentProcess^.Status := WaitOnSem ;        (* set new status     *)
         CurrentProcess^.Which := s ;                 (* debugging aid      *)
         Reschedule                                   (* find next process  *)
      END
   END ;
(* ToOldState := TurnInterrupts(ToOldState)           (* restore interrupts *) *)
END Wait ;


(*
   Signal - performs dijkstra's V operation on a semaphore.
            A process which calls the procedure will increment
            the semaphores value.
*)

PROCEDURE Signal (s: SEMAPHORE) ;
VAR
   ToOldState: PROTECTION ;
   d         : DESCRIPTOR ;
BEGIN
(* ToOldState := TurnInterrupts(MAX(PROTECTION)) ;                (* disable interrupts *) *)
   WITH s^ DO
      IF Who=NIL
      THEN
         INC( Value )                                 (* no process waiting *)
      ELSE
         d := SubFromSemaphoreTop(Who) ; (* remove process from semaphore q *)
         d^.Which := NIL ;              (* no longer waiting on semaphore   *)
         d^.Status := Runnable ;        (* set new status                   *)
         AddToReady(d) ;                (* add process to the run queue     *)
         Reschedule                     (* find out whether there is a      *)
                                        (* higher priority to run.          *)
      END
   END ;
(* ToOldState := TurnInterrupts(ToOldState)           (* restore interrupts *) *)
END Signal ;


(*
   WaitForIO - waits for an interrupt to occur on vector, VectorNo.
*)

PROCEDURE WaitForIO (VectorNo: CARDINAL) ;
VAR
   Calling   : DESCRIPTOR ;
   Next      : PROCESS ;
   ToOldState: PROTECTION ;
   r         : INTEGER ;
BEGIN
(* ToOldState := TurnInterrupts(MAX(PROTECTION)) ; *)
(*
   DebugString('inside WaitForIO ') ;
   DebugString(CurrentProcess^.RunName) ;
   DebugString('\n') ;
*)
   Assert(CurrentProcess^.Status=Runnable,
          __FILE__, __LINE__, __FUNCTION__) ;
   SubFromReady(CurrentProcess) ;   (* remove process from run queue *)
   (*
      alter run priority to hi as all processes waiting for an interrupt
      are scheduled to run at the highest priority.
   *)
   WITH CurrentProcess^ DO
      Status := WaitOnInt ;   (* it will be blocked waiting for an interrupt.  *)
      RunPriority := hi ;     (* this (hopefully) allows it to run as soon as  *)
                              (* the interrupt occurs.                         *)
   END ;
   Calling := CurrentProcess ;      (* process which called WaitForIO          *)
   CurrentProcess := NextReady() ;  (* find next process to run while we wait  *)
   Next := CurrentProcess^.Volatiles ;
   (*
      This is quite complicated. We transfer control to the next process saving
      our volatile environment into the Calling process descriptor volatiles.
      When an interrupt occurs the calling process will be resumed and the
      interrupted process volatiles will be placed into Next.
   *)
   IOTRANSFER(Calling^.Volatiles, Next, VectorNo) ;

   (*
      At this point the interrupt has just occurred and the volatiles of
      the interrupted process are in Next. Next is the current process
      and so we must save them before picking up the Calling descriptor.
   *)

   CurrentProcess^.Volatiles := Next ;             (* carefully stored away *)
   CurrentProcess := Calling ;                     (* update CurrentProcess *)
(*
   DebugString(CurrentProcess^.RunName) ;
*)
   CurrentProcess^.Status := Runnable ;            (* add to run queue      *)
   AddToReady(CurrentProcess) ;
(*
   DebugString(' finishing WaitForIO\n') ;
*)

(* ToOldState := TurnInterrupts(ToOldState)           (* restore interrupts *) *)
END WaitForIO ;


(*
   Ps - displays a process list together with relevant their status.
*)

PROCEDURE Ps ;
VAR
   ToOldState: PROTECTION ;
   p         : DESCRIPTOR ;
   s         : SEMAPHORE ;
   a         : ARRAY [0..5] OF CHAR ;
BEGIN
(* ToOldState := TurnInterrupts(MAX(PROTECTION)) ;                (* disable interrupts *) *)
   p := ExistsQueue ;
   IF p#NIL
   THEN
      REPEAT
         DisplayProcess(p) ;
         p := p^.ExistsQ.Right
      UNTIL p=ExistsQueue
   END ;
   s := AllSemaphores ;
   IF s#NIL
   THEN
      REPEAT
         WITH s^ DO
            DebugString(SemName) ;
            WriteNSpaces(MaxCharsInName-StrLen(SemName)) ;
            CardToStr(Value, 0, a) ;
            DebugString(a) ;
            DebugString('\n')
         END ;
         s := s^.ExistsQ.Right
      UNTIL s=AllSemaphores
   END ;
(* ToOldState := TurnInterrupts(ToOldState)           (* restore interrupts *) *)
END Ps ;


(*
   DisplayProcess - displays the process, p, together with its status.
*)

PROCEDURE DisplayProcess (p: DESCRIPTOR) ;
VAR
   a: ARRAY [0..4] OF CHAR ;
BEGIN
   WITH p^ DO
      DebugString(RunName) ; WriteNSpaces(MaxCharsInName-StrLen(RunName)) ;
      CASE RunPriority OF

      idle: DebugString(' idle ') |
      lo  : DebugString(' lo   ') |
      hi  : DebugString(' hi   ')

      END ;
      CASE Status OF

      Runnable : DebugString('runnable ') |
      Suspended: DebugString('suspended') |
      WaitOnSem: DebugString('waitonsem   (') ;
                 DebugString(Which^.SemName) ;
                 DebugString(')') |
      WaitOnInt: DebugString('waitonint')

      END ;
      DebugString('\n')
   END
END DisplayProcess ;


(*
   WriteNSpaces - writes, n, spaces.
*)

PROCEDURE WriteNSpaces (n: CARDINAL) ;
BEGIN
   WHILE n>0 DO
      DebugString(' ') ;
      DEC(n)
   END
END WriteNSpaces ;


(*
   GetCurrentProcess - returns the descriptor of the current running
                       process.
*)

PROCEDURE GetCurrentProcess () : DESCRIPTOR ;
VAR
   ToOldState: PROTECTION ;
   p         : DESCRIPTOR ;
BEGIN
(* ToOldState := TurnInterrupts(MAX(PROTECTION)) ;      (* disable interrupts *) *)
   p := CurrentProcess ;
(* ToOldState := TurnInterrupts(ToOldState) ;         (* restore interrupts *) *)
   RETURN( p )
END GetCurrentProcess ;


(*
   RotateRunQueue - rotates the process run queue.
*)

PROCEDURE RotateRunQueue ;
VAR
   ToOldState: PROTECTION ;
BEGIN
(* ToOldState := TurnInterrupts(MAX(PROTECTION)) ;                (* disable interrupts *) *)
   (* we only need to rotate the lo priority processes as:
      idle - should only have one process (the idle process)
      hi   - are the device drivers which most of the time are performing
             WaitForIO
   *)
   IF RunQueue[lo]#NIL
   THEN
      RunQueue[lo] := RunQueue[lo]^.ReadyQ.Right
   END ;
(* ToOldState := TurnInterrupts(ToOldState)           (* restore interrupts *) *)
END RotateRunQueue ;


(*
   ProcessName - displays the name of process, d, through
                 DebugString.
*)

PROCEDURE ProcessName (d: DESCRIPTOR) ;
BEGIN
   DebugString(d^.RunName)
END ProcessName ;


(*
   DebugProcess -
*)

PROCEDURE DebugProcess (d: DESCRIPTOR) ;
VAR
   ToOldState: PROTECTION ;
BEGIN
(* ToOldState := TurnInterrupts(MAX(PROTECTION)) ; *)
   WITH d^ DO
      IF Status=WaitOnSem
      THEN
         DebugString('debugging process (') ;
         DebugString(RunName) ;
         DebugString(') was waiting on semaphore (') ;
         DebugString(Which^.SemName) ;
         DebugString(')\n') ;
         SubFromSemaphore(Which^.Who, d) ;
         AddToReady(d) ;
         Status := Runnable ;
         Debugged := TRUE ;
         Reschedule
      ELSE
         DebugString('can only debug deadlocked processes (') ;
         DebugString(RunName) ;
         DebugString(') which are waiting on a semaphore\n')
      END
   END ;
(* ToOldState := TurnInterrupts(ToOldState) *)
END DebugProcess ;


(*
   CheckDebugged - checks to see whether the debugged flag has
                   been set by the debugger.
                   TRUE  is returned if the process was debugged.
                   FALSE is returned if the process was not debugged.
*)

PROCEDURE CheckDebugged () : BOOLEAN ;
BEGIN
   WITH CurrentProcess^ DO
      IF Debugged
      THEN
         (*
            You will see this comment after you have enabled a
            deadlocked process to continue via the gdb command:

            print Executive_DebugProcess(d)

            debugger caused deadlocked process to continue
         *)
         (* gdb.breakpoint ; *)
         Debugged := FALSE ;
         SubFromReady(CurrentProcess) ;
         AddToSemaphore(Which^.Who, CurrentProcess) ;
                                           (* add it back to the queue sem *)
         Status := WaitOnSem ;

         RETURN( TRUE )
      END
   END ;
   RETURN( FALSE )
END CheckDebugged ;


(*
   Reschedule - reschedules to the highest runnable process.
*)

PROCEDURE Reschedule ;
BEGIN
   (*
      the repeat loop allows us to debug a process even when it is
      technically waiting on a semaphore. We run the process into
      a breakpoint and then back into this schedule routine.
      This is really useful when trying to find out why processes have
      deadlocked.
   *)
   REPEAT
      ScheduleProcess
   UNTIL NOT CheckDebugged()
END Reschedule ;


(*
   ScheduleProcess - finds the highest priority Runnable process and
                     then transfers control to it.
*)

PROCEDURE ScheduleProcess ;
VAR
   From,
   Highest: DESCRIPTOR ;
BEGIN
   Highest := NextReady() ;

   (* rotate ready Q to ensure fairness *)
   RunQueue[Highest^.RunPriority] := Highest^.ReadyQ.Right ;

   (* no need to transfer if Highest=CurrentProcess *)
   IF Highest#CurrentProcess
   THEN
      From := CurrentProcess ;
(*
      DebugString('context switching from ') ; DebugString(From^.RunName) ;
*)
      (* alter CurrentProcess before we TRANSFER *)
      CurrentProcess := Highest ;
(*
      DebugString(' to ') ; DebugString(CurrentProcess^.RunName) ;
*)

      TRANSFER(From^.Volatiles, Highest^.Volatiles) ;
(*
      ; DebugString(' (') ; DebugString(CurrentProcess^.RunName) ;
      DebugString(')\n') ;
*)
      CheckGarbageCollect
   END
END ScheduleProcess ;


(*
   NextReady - returns the highest priority Runnable process.
*)

PROCEDURE NextReady () : DESCRIPTOR ;
VAR
   Highest: DESCRIPTOR ;
   Pri    : Priority ;
BEGIN
   Highest := NIL ;
   FOR Pri := idle TO hi DO
      IF RunQueue[Pri]#NIL
      THEN
         Highest := RunQueue[Pri]
      END
   END ;
   Assert(Highest#NIL, __FILE__, __LINE__, __FUNCTION__) ;
   RETURN( Highest )
END NextReady ;


(*
   CheckGarbageCollect - checks to see whether GarbageItem is set
                         and if so it deallocates storage associated
                         with this descriptor.
*)

PROCEDURE CheckGarbageCollect ;
BEGIN
   IF GarbageItem#NIL
   THEN
      WITH GarbageItem^ DO
         DEALLOCATE(Start, Size)
      END ;
      DISPOSE(GarbageItem) ;
      GarbageItem := NIL
   END
END CheckGarbageCollect ;


(*
   AddToExists - adds item, Item, to the exists queue.
*)

PROCEDURE AddToExists (Item: DESCRIPTOR) ;
BEGIN
   IF ExistsQueue=NIL
   THEN
      ExistsQueue := Item ;                  (* Head is empty therefore make *)
      Item^.ExistsQ.Left := Item ;           (* Item the only entry on this  *)
      Item^.ExistsQ.Right := Item            (* queue.                       *)
   ELSE
      Item^.ExistsQ.Right := ExistsQueue ;   (* Add Item to the end of queue *)
      Item^.ExistsQ.Left  := ExistsQueue^.ExistsQ.Left ;
      ExistsQueue^.ExistsQ.Left^.ExistsQ.Right := Item ;
      ExistsQueue^.ExistsQ.Left := Item
   END
END AddToExists ;


(*
   SubFromExists - removes a process, Item, from the exists queue, Head.
*)

PROCEDURE SubFromExists (VAR Head: DESCRIPTOR; Item: DESCRIPTOR) ;
BEGIN
   IF (Item^.ExistsQ.Right=Head) AND (Item=Head)
   THEN
      Head := NIL
   ELSE
      IF Head=Item
      THEN
         Head := Head^.ExistsQ.Right
      END ;
      Item^.ExistsQ.Left^.ExistsQ.Right := Item^.ExistsQ.Right ;
      Item^.ExistsQ.Right^.ExistsQ.Left := Item^.ExistsQ.Left
   END
END SubFromExists ;


(*
   AddToSemaphore - adds item, Item, to the semaphore queue defined by Head.
*)

PROCEDURE AddToSemaphore (VAR Head: DESCRIPTOR; Item: DESCRIPTOR) ;
BEGIN
   IF Head=NIL
   THEN
      Head := Item ;                         (* Head is empty therefore make *)
      Item^.SemaphoreQ.Left := Item ;        (* Item the only entry on this  *)
      Item^.SemaphoreQ.Right := Item         (* queue.                       *)
   ELSE
      Item^.SemaphoreQ.Right := Head ;       (* Add Item to the end of queue *)
      Item^.SemaphoreQ.Left  := Head^.SemaphoreQ.Left ;
      Head^.SemaphoreQ.Left^.SemaphoreQ.Right := Item ;
      Head^.SemaphoreQ.Left := Item
   END
END AddToSemaphore ;


(*
   AddToSemaphoreExists - adds item, Item, to the semaphore exists queue.
*)

PROCEDURE AddToSemaphoreExists (Item: SEMAPHORE) ;
BEGIN
   IF AllSemaphores=NIL
   THEN
      AllSemaphores := Item ;                (* Head is empty therefore make *)
      Item^.ExistsQ.Left := Item ;           (* Item the only entry on this  *)
      Item^.ExistsQ.Right := Item            (* queue.                       *)
   ELSE
      Item^.ExistsQ.Right := AllSemaphores ;
                                             (* Add Item to the end of queue *)
      Item^.ExistsQ.Left  := AllSemaphores^.ExistsQ.Left ;
      AllSemaphores^.ExistsQ.Left^.ExistsQ.Right := Item ;
      AllSemaphores^.ExistsQ.Left := Item
   END
END AddToSemaphoreExists ;


(*
   AddToReady - adds item, Item, to the ready queue.
*)

PROCEDURE AddToReady (Item: DESCRIPTOR) ;
BEGIN
   AddToReadyQ(RunQueue[Item^.RunPriority], Item)
END AddToReady ;


(*
   AddToReadyQ - adds item, Item, to the ready queue defined by Head.
*)

PROCEDURE AddToReadyQ (VAR Head: DESCRIPTOR; Item: DESCRIPTOR) ;
BEGIN
   IF Head=NIL
   THEN
      Head := Item ;                         (* Head is empty therefore make *)
      Item^.ReadyQ.Left := Item ;            (* Item the only entry on this  *)
      Item^.ReadyQ.Right := Item             (* queue.                       *)
   ELSE
      Item^.ReadyQ.Right := Head ;           (* Add Item to the end of queue *)
      Item^.ReadyQ.Left  := Head^.ReadyQ.Left ;
      Head^.ReadyQ.Left^.ReadyQ.Right := Item ;
      Head^.ReadyQ.Left := Item
   END
END AddToReadyQ ;


(*
   SubFromReady - subtract process descriptor, Item, from the Ready queue.
*)

PROCEDURE SubFromReady (Item: DESCRIPTOR) ;
BEGIN
   SubFromReadyQ(RunQueue[Item^.RunPriority], Item)
END SubFromReady ;


(*
   SubFromReadyQ - removes a process, Item, from a queue, Head.
*)

PROCEDURE SubFromReadyQ (VAR Head: DESCRIPTOR; Item: DESCRIPTOR) ;
BEGIN
   IF (Item^.ReadyQ.Right=Head) AND (Item=Head)
   THEN
      Head := NIL
   ELSE
      IF Head=Item
      THEN
         Head := Head^.ReadyQ.Right
      END ;
      Item^.ReadyQ.Left^.ReadyQ.Right := Item^.ReadyQ.Right ;
      Item^.ReadyQ.Right^.ReadyQ.Left := Item^.ReadyQ.Left
   END
END SubFromReadyQ ;


(*
   SubFromSemaphoreTop - returns the first descriptor in the
                         semaphore queue.
*)

PROCEDURE SubFromSemaphoreTop (VAR Head: DESCRIPTOR) : DESCRIPTOR ;
VAR
   Top: DESCRIPTOR ;
BEGIN
   Top := Head ;
   SubFromSemaphore(Head, Top) ;
   RETURN( Top )
END SubFromSemaphoreTop ;


(*
   SubFromSemaphore - removes a process, Item, from a queue, Head.
*)

PROCEDURE SubFromSemaphore (VAR Head: DESCRIPTOR; Item: DESCRIPTOR) ;
BEGIN
   IF (Item^.SemaphoreQ.Right=Head) AND (Item=Head)
   THEN
      Head := NIL
   ELSE
      IF Head=Item
      THEN
         Head := Head^.SemaphoreQ.Right
      END ;
      Item^.SemaphoreQ.Left^.SemaphoreQ.Right := Item^.SemaphoreQ.Right ;
      Item^.SemaphoreQ.Right^.SemaphoreQ.Left := Item^.SemaphoreQ.Left
   END
END SubFromSemaphore ;


(*
   Idle - this process is only run whenever there is no other Runnable
          process. It should never be removed from the run queue.
*)

PROCEDURE Idle ;
VAR
   ToOldState: PROTECTION ;
BEGIN
   ToOldState := TurnInterrupts(MIN(PROTECTION)) ;    (* enable interrupts *)
   LOOP
      (*
         Listen for interrupts.
         We could solve chess endgames here or calculate PI etc.
         We forever wait for an interrupt since there is nothing else
         to do...
      *)
      ListenLoop
   END
   (* we must NEVER exit from the above loop *)
END Idle ;


(*
   InitIdleProcess - creates an idle process descriptor which
                     is run whenever no other process is Runnable.
                     The Idle process should be the only process which
                     has the priority idle.
*)

VAR
   IdleProcess: DESCRIPTOR ;              (* Idle process always runnable  *)

PROCEDURE InitIdleProcess ;
VAR
   db         : ARRAY [0..80] OF CHAR ;
BEGIN
   NEW(IdleProcess) ;
   WITH IdleProcess^ DO
      ALLOCATE(Start, IdleStackSize) ;
      Size := IdleStackSize ;
      NEWPROCESS(Idle, Start, IdleStackSize, Volatiles) ;
      InitQueue(SemaphoreQ) ;             (* not on a semaphore queue     *)
      Which      := NIL ;                 (* at all.                      *)
      StrCopy('Idle', RunName) ;          (* idle process's name          *)
      Status := Runnable ;                (* should always be idle        *)
      RunPriority := idle ;               (* lowest priority possible     *)
      Debugged    := FALSE ;              (* should never be debugging    *)
   END ;
   AddToReady(IdleProcess) ;              (* should be the only           *)
                                          (* process at this run priority *)
   AddToExists(IdleProcess)               (* process now exists..         *)
END InitIdleProcess ;


(*
   InitInitProcess - creates a descriptor for this running environment
                     so it too can be manipulated by Reschedule.

                     This concept is important to understand.
                     InitInitProcess is called by the startup code to this
                     module. It ensures that the current stack and processor
                     volatiles can be "housed" in a process descriptor and
                     therefore it can be manipulated just like any other
                     process.
*)

PROCEDURE InitInitProcess ;
BEGIN
   NEW(CurrentProcess) ;
   WITH CurrentProcess^ DO
      Size := 0 ;              (* we dont know the size of main stack  *)
      Start := NIL ;           (* we don't need to know where it is.   *)
      InitQueue(ReadyQ) ;      (* assign queues to NIL                 *)
      InitQueue(ExistsQ) ;
      InitQueue(SemaphoreQ) ;  (* not waiting on a semaphore queue yet *)
      Which       := NIL ;     (* at all.                              *)
      StrCopy('Init', RunName) ;  (* name for debugging purposes       *)
      Status      := Runnable ;   (* currently running                 *)
      RunPriority := lo ;         (* default status                    *)
      Debugged    := FALSE ;      (* not deadlock debugging yet        *)
   END ;
   AddToExists(CurrentProcess) ;
   AddToReady(CurrentProcess)
END InitInitProcess ;


(*
   InitQueue - initializes a queue, q, to empty.
*)

PROCEDURE InitQueue (VAR q: DesQueue) ;
BEGIN
   WITH q DO
      Right := NIL ;
      Left  := NIL
   END
END InitQueue ;


(*
   Init - initializes all the global variables.
*)

PROCEDURE Init ;
BEGIN
   ExistsQueue := NIL ;
   RunQueue[lo] := NIL ;
   RunQueue[hi] := NIL ;
   RunQueue[idle] := NIL ;
   AllSemaphores := NIL ;
   GarbageItem := NIL ;
   InitInitProcess ;
   InitIdleProcess
END Init ;


BEGIN
   Init
END Executive.