DEFINITION MODULE TaskControl;

	(****************************************************************)
	(*								*)
	(*   Data structures internal to the kernel of the operating	*)
	(*     system; the dispatcher of the operating system; and	*)
	(*		    related procedures.				*)
	(*								*)
	(*	This version supports priority inheritance.		*)
	(*								*)
	(*	Programmer:	P. Moylan				*)
	(*	Last edited:	16 March 1995				*)
	(*	Status:		OK					*)
	(*								*)
	(****************************************************************)

FROM SYSTEM IMPORT
    (* type *)	ADDRESS;

(************************************************************************)
(*									*)
(*			   END-USER PROCEDURES				*)
(*									*)
(************************************************************************)

CONST MaxPriority = 15;

TYPE
    PriorityLevel = [0..MaxPriority];
    NameString = ARRAY [0..15] OF CHAR;

PROCEDURE CreateTask (StartAddress: PROC;  taskpriority: PriorityLevel;
						taskname: NameString);

    (* Must be called to introduce a task to the system. The first	*)
    (* parameter, which should be the name of a procedure containing	*)
    (* the task code, gives the starting address.  The second parameter	*)
    (* is the task's base priority.  If this task has a higher priority	*)
    (* than its creator, it will run immediately.  Otherwise, it	*)
    (* becomes ready.							*)

    (* The effective priority of a task can be higher than its base	*)
    (* priority, as the result of priority inheritance.  This happens	*)
    (* when the task holds a lock on which a higher-priority task is	*)
    (* blocked.								*)

    (* NOTE: If time-slicing is enabled, tasks of equal priority share	*)
    (* processor time on a round-robin basis.  To disable this feature,	*)
    (* set the constant TimeSlicingEnabled (in the ConfigurationOptions	*)
    (* module) to FALSE.						*)

    (* Tasks of different priorities never share time.  When a		*)
    (* high-priority task becomes able to run, there is an immediate	*)
    (* task switch.							*)

    (* A task terminates itself either by an explicit call to TaskExit,	*)
    (* or simply by falling out of the bottom of its code.		*)
    (* There is no provision for tasks to kill other tasks.  Suicide	*)
    (* is legal, but murder is not.					*)

PROCEDURE UsingFloatingPoint;

    (* Tells the kernel that this task is one which performs floating	*)
    (* point operations.  The consequence is that the state of the	*)
    (* (physical or emulated, as applicable) floating point processor	*)
    (* is saved on a task switch.  This call is usually unnecessary;	*)
    (* the default assumption is that interrupt tasks do not perform	*)
    (* floating point arithmetic but that all other tasks may.		*)
    (* NOTE: It is never acceptable for an interrupt task to call this	*)
    (* procedure.							*)

PROCEDURE NotUsingFloatingPoint;

    (* Tells the kernel that this task does not perform any floating	*)
    (* point operations.  Calling this (optional) procedure speeds up	*)
    (* task switching slightly, but it does put the onus on the caller	*)
    (* to be certain that it does no floating point operations.  If you	*)
    (* call this procedure and then perform floating point arithmetic	*)
    (* anyway, you can get severe and erratic floating point errors.	*)

PROCEDURE TaskExit;

    (* Removes the currently running task from the system, and performs	*)
    (* a task switch to the next ready task.				*)

    (* There is normally no need for a task to call this procedure,	*)
    (* because it is automatically called when the task code "falls out	*)
    (* the bottom" by executing its final procedure return.  The stack	*)
    (* is set up, at the time a task is created, in such a way that	*)
    (* TaskExit will be entered at that time.				*)

(************************************************************************)
(*									*)
(*		  LOCKS FOR CRITICAL SECTION PROTECTION			*)
(*									*)
(*  Note that we distinguish between a Lock and a Semaphore.		*)
(*  A Semaphore is a general semaphore - whose operations are defined	*)
(*  in module Semaphores - which can be used for general inter-task	*)
(*  interlocking.  A Lock is similar to a binary semaphore (with a	*)
(*  more efficient implementation than a Semaphore), but may be used	*)
(*  only in a strictly nested fashion and is therefore useful only	*)
(*  for critical section protection.  No task should perform a		*)
(*  semaphore Wait while it holds a Lock.  Priority inheritance is	*)
(*  used for Locks - that is, a task holding a Lock will have its	*)
(*  priority temporarily increased as long as it is blocking another	*)
(*  task of higher priority - but not for Semaphores.			*)
(*									*)
(************************************************************************)

TYPE Lock;	(* is private *)

PROCEDURE CreateLock (VAR (*OUT*) L: Lock);

    (* Creates a new lock. *)

PROCEDURE DestroyLock (VAR (*INOUT*) L: Lock);

    (* Disposes of a lock. *)

PROCEDURE Obtain (L: Lock);

    (* Obtains lock L, waiting if necessary. *)

PROCEDURE Release (L: Lock);

    (* Releases lock L - which might unblock some other task. *)

PROCEDURE ReleaseAllLocks;

    (* Releases all locks held by the current task.  Application-level	*)
    (* tasks normally won't need to call this procedure; it is		*)
    (* provided to support the system shutdown function and for things	*)
    (* like "emergency abort" operations.				*)

(************************************************************************)
(*									*)
(*		    SUPPORT FOR INTERRUPT HANDLERS			*)
(*									*)
(************************************************************************)

PROCEDURE CreateInterruptTask (InterruptNumber: CARDINAL;
				StartAddress: PROC;  taskname: NameString);

    (* Introduces an interrupt task to the system.  The first parameter	*)
    (* is the hardware-defined interrupt number, and the second is the	*)
    (* address of the procedure whose code is the interrupt handler.	*)
    (* An interrupt task differs from an ordinary task in that, when it	*)
    (* is not running, it is idle rather than ready, and the dispatcher	*)
    (* does not consider it to be among the tasks eligible to run.	*)
    (* Rather, it is run by a task switch which is made directly by the	*)
    (* assembly language routine which fields the interrupt.  When the	*)
    (* interrupt task has responded to the interrupt, it must call	*)
    (* procedure WaitForInterrupt to put itself back in the idle state.	*)
    (* On the next interrupt, it will continue from just after the call	*)
    (* to WaitForInterrupt.  Normally, therefore, the interrupt task	*)
    (* will be written as an infinite loop.  If for any reason the	*)
    (* interrupt task exits by falling out of the bottom of its code,	*)
    (* it will be destroyed in the same way as a normal task which	*)
    (* terminates.  That could be fatal, unless steps have been taken	*)
    (* to reset the interrupt vector.					*)

PROCEDURE KillInterruptTask (InterruptNumber: CARDINAL);

    (* Removes an interrupt task from the system.  This should not be	*)
    (* called from inside an interrupt task.				*)

(************************************************************************)
(*									*)
(*			DEVICE DRIVER SUPPORT				*)
(*									*)
(*  The following procedure may be called only by an interrupt task.	*)
(*									*)
(************************************************************************)

PROCEDURE WaitForInterrupt;

    (* Called by an interrupt task, to make itself dormant until the	*)
    (* next interrupt comes along.  It is not necessary to specify	*)
    (* the interrupt number, since this was fixed at the time the	*)
    (* interrupt task was created.					*)

    (* Warning: this procedure should never be called by a task which	*)
    (* is not an interrupt task.					*)

(************************************************************************)
(*									*)
(*		    PROCEDURES PRIVATE TO THE KERNEL			*)
(*									*)
(*  The remaining declarations in this module are needed because the	*)
(*  kernel is made up of several modules.  (Unfortunately, there is no	*)
(*  way in Modula-2 to export something to a separately compiled module	*)
(*  without making it visible to everyone; so please close your eyes	*)
(*  at this point.)  The procedures declared here should be called only	*)
(*  from the innermost parts of the operating system.			*)
(*									*)
(************************************************************************)

TYPE TaskQueue;	(* is private *)

PROCEDURE CreateQueue (VAR (*OUT*) KQ: TaskQueue);

    (* Creates an initially empty queue.				*)

PROCEDURE MarkAsReady (VAR (*INOUT*) FromQ: TaskQueue);

    (* Takes the first task from queue FromQ and puts it on the ready	*)
    (* list, or runs it immediately if it has higher priority than the	*)
    (* task which called MarkAsReady.					*)

PROCEDURE QueueAndSwitchTasks (VAR (*INOUT*) KQ: TaskQueue);

    (* Puts the current task onto list KQ, and switches to the		*)
    (* highest-priority ready task.					*)

(************************************************************************)
(*									*)
(*			     TIMER OPERATIONS				*)
(*									*)
(*   These procedures are called by the Timer and Semaphores modules	*)
(*									*)
(************************************************************************)

PROCEDURE Delay (sleeptime: INTEGER);

    (* Puts the calling task to sleep for the specified number of clock	*)
    (* ticks.								*)

PROCEDURE QueueWithTimeout (VAR (*INOUT*) KQ: TaskQueue;
					TimeLimit: INTEGER): BOOLEAN;

    (* Like procedure QueueAndSwitchTasks, this procedure puts the	*)
    (* current task on the tail of list KQ, and gives control to the	*)
    (* highest-priority ready task.  The difference is that we allow	*)
    (* this task to remain on KQ for at most TimeLimit timer ticks.	*)
    (* If the task is removed from KQ before the time limit expires,	*)
    (* we return a result of FALSE.  If the time limit expires first,	*)
    (* we remove the queued task from KQ anyway, and make it runnable,	*)
    (* and return a result of TRUE when it does run.			*)
    (* Note: this procedure may be called only from inside the kernel.	*)

PROCEDURE CheckSleepers;

    (* Called from the timer interrupt routine, to deal with sleeping	*)
    (* tasks.  Warning: there must be no kernel call between a call to	*)
    (* this procedure and the next call to WaitForInterrupt.		*)

PROCEDURE TimeSliceCheck;

    (* Called from the timer interrupt routine, to check whether the	*)
    (* current task has used up its time slice, and to perform a task	*)
    (* switch if so.							*)

    (* This procedure does nothing if time-slicing is disabled.		*)
    (* Time-slicing is controlled by a constant TimeSlicingEnabled	*)
    (* in module ConfigurationOptions.					*)

END TaskControl.
