
file :  /kb/timerdsgn.text
date :  29-MAR-1982   kb

This is the design document for the TIMER driver.

The Timer driver performs 3 services. First, it maintains a table
of user timer service routines which on a 50 millisecond clock
interrupt it invokes.  Second, it uses the VIA timer number 2 to
produce a Bell sound (e.g. control G).  Last, the driver will
read and write the time and date clock.

The first service, 50 milliseconds timer call, is setup by the
Timer unitstatus routine.  The serviced routines are invoked
during the VIA timer #1 interrupt service procedure.  This
service call is not a precise 50 ms. interval call.  Users who
have very time critical processes must setup VIA timer #2 and
monitor its activity.  Timer #1 is for the exclusive use of the
Timer driver.  Therefore, do not use it for user controlled
timing activities.

The Timer driver unitstatus performs 4 table manipulation actions
depending on the value of the input parameters.  OS defined
parameters for the unitstatus procedure are : unit number (word),
buffer address (long word), and control (word) .  The Timer
driver's 4 actions are Create, Enable, Disable and Delete table
entry.  The Timer driver selects the correct action by the value
passed as the control parameter. The control parameter values are
1 for Create, 2 for Delete, 3 for Disable and 4 for Enable. The
remaining parameters to the procedure are passed in a parameter
block, whose address is passed as the unitstatus parameter buffer
address. Space in the parameter block must be made for Output
values as well as Input values.

The following is a description of the parameters in each of the
table manipulation procedure's parameter blocks.

Create table entry function takes as input an address of the user
service routine (long word), the count of 50 ms. periods before
interrupt service routine call (word) and a word of Flags and
returns a table entry ID (word) of the entry made.  The count
ranges from 1 to 65,535, where a 1 value means make a call to the
user service routine every 50 millisecond interval timer
interrupt.  Currently 2 bit flags are defined in the word of
Flags.  The first is the Continuous/One-shot flag.  When it is
clear the table entry is used until it is removed by a Delete
call. Consequently, everytime the entry's down counter is less
than 0 (zero) the associated user's service routine is called.
When the flag is set the entry is used in a one-shot mode.
Therefore, when the user service routine returns the associated
table entry in this mode is automatically Deleted.  The second is
Skip First Call flag.  This flag tells the Timer driver to ignore
the first call to be made to the corresponding user service
routine when the down counter reaches less than 0 (zero). Since
the Create operation is performed asynchronously to the
interrupts generated by the free running interval timer (VIA
timer #2), it is likely that the first user service routine call
for this entry with this flag clear will occur in less time than
the requested interval specified by the Count.  Therefore, the
user should use this flag when a minimum delay time of the count
of interrupt periods passed in the Create function call is
needed.  The Create function has 3 input parameters and 1 output
parameter for a total of 10 bytes, which must start on a even
word boundary.

Create Parameter block in Pascal fragment :

CONST
        OneShot   = 2;
        SkipFirst = 4;

TYPE    record
                UserServiceRtn : pointer; {Input parameter}
                Count          : integer; {Input parameter}
                Flags          : integer; {Input parameter}
                TableID        : integer; {Output parameter}
                end;{record}

Delete table entry procedure takes the table entry ID (word) for
the entry desired and deletes that entry from the table.  This
table entry ID is the same value returned by the Create function.
The Delete procedure has 1 input parameter and no (zero,0) output
parameters, for a total of 2 bytes.

The Disable procedure allows the user to prevent the Timer
interrupt routine from calling the user's timer service routine
without Deleting the user's table entry.  The procedure has one
input parameter, the table entry ID of the entry to disable. It
has no output parameters.  The total number of bytes for this
procedure's parameter block is 2.

The Enable procedure allows the user to restart a table entry. It
clears the Disable condition and resets the down counter to it's
starting condition. It can be used for either starting up a
Disabled table entry or restarting a running entry before it
causes a call to the service routine.  This procedure has only 1
parameter, the table entry ID, for a total of 2 bytes.

All the actions will place error condition codes within the
IORESULT system variable.  Only two error codnditions are
defined.  They are a) table full and b) no such entry (or invalid
table entry ID).

User timer service routines are called during the interrupt
processing.  They should take a minimal amount of time; I suggest
less than 500 microseconds.  Therefore, don't use them for doing
any significant processing.  They would be best used for setting
flags informing blocked processes that an event has occurred.

The table operations are not indivisible.  Therefore, a user
timer service routine may be called while the user is making a
Delete or Disable call.  Consequently, any operation done by the
user service routine must be easily reversable after the table
operation call.  This only reenforces the need for keeping the
user service routines simple with few destructive operations.

All user timer service routines must return to the Timer driver
interrupt routine via the RTS instruction.  Also, no arguement
passing is available to the the user's timer service routine.

For Pascal programs which want to have a user timer interrupt
service routine there are a few restrictions.  First, the
procedure, NOT a function, to be used as the service routine must
be a global procedure.  Functions cannot be used as user timer
service routines.  The procedure must return to the Timer driver
and cannot use Global GOTO's.  When the table entry is created,
the routine that calls the Timer driver Create function must be a
resident part of the program that the service routine procedure
is a part of.

The second service, the Bell, is also performed by the Timer
unitstatus function.  The unitstatus parameters must be set as
follows :  Control must be set to 0 and buffer address must point
to the Bell parameter block.  The parameter block contains 3
parameters, Bell frequency (word), pattern of 8 on and offs of
the speaker (byte), a filler byte to keep block to even number of
bytes, and the duration in counts of Timer #1 interrupt periods
(50 milliseconds), a word.  The Bell parameter block is 6 bytes.
The pattern of 8 on and offs of the speaker is a byte with a bit
equal to 1 for speaker on and 0 for speaker off. The Timer driver
uses VIA timer #2 non-exclusively.

The third service, the time and date clock, is accessed via the
Unitread and Unitwrite driver interfaces.  The clock interfaces
require a Clock Parameter Block.  This is a fixed length, fixed
format interface.  Unlike other drivers, the timer driver cannot
do reads and writes of variable length data strings.  The Clock
Parameter Block is one field longer for the Unitwrite procedure
then for the Unitread procedure.  The following is a Pascal
fragment which describes the Clock Parameter Block.

               type
                  clockPB = record
                              DayofWeek : 1..7;
                              Month : 1..12;
                              Day : 1..31;
                              Hour : 0..23;
                              Mins : 0..59;
                              Secs : 0..59;
                              Tenths : 0..9;
                              case INTEGER of
                                0 : ( {Write only} LeapYear : 0..3);
                               end;{variant}
                             end;{record}

Though each field is a sub range of the integer type and could
fit in less space then a word each field MUST be a word long.
Therefore, declare each field as an integer, but restrict your
useage to the valid range.  The driver does validate the range in
case of a user error. Unfortunately, the Day parameter is not
completely verified.  If the month specified is Febraury (Month
:= 2), but the Day is 30, the driver does not report an error.

Except for the fields DayofWeek, Tenths, and LeapYear the field
meanings are self-explanatory.  DayofWeek specifies which day it
is, such as Monday, Tuesday, etc.  The value 1 specifies Sunday
while 7 specifies Saturday.  The field Tenths is tenths of a
second.  Finally, the clock has no register set for years.
However, to keep the correct date after a year change the clock
tracks if this year is a leap year.  The LeapYear field specifies
the number of years AFTER the last leap year.  Therefore, if this
year is leap year then the field should be assigned to 0 (zero).
Consequently, if last year was leap year then the field should be
assigned to 1 (one), and so forth.  The LeapYear field is only
used when setting the clock (Unitwrite). It is not returned when
reading the clock (Unitread).

To set the clock, first fill in all the fields in the Clock
Parameter Block, including the LeapYear field.  Next, call the
timer driver Unitwrite to set the clock.

                Unitwrite( TimerDriverNumber, WriteParmBlock,
                LengthWriteParmBlock );

The driver will set the IORESULT variable if the parameter block
is in error.

To read the clock just call the timer driver Unitread with the
Clock Parameter Block.

                Unitread( TimerDriverNumber, ReadParmBlock,
                LengthReadParmBlock );

The correct length of the Read version of the Clock Paramter
Block must be passed to the driver.  If it is to short, an error
is returned in IORESULT and the clock is NOT read.

The Timer driver unitinstall procedure sets-up the VIA,
initializes the timer table to no entries value, starts the 50
ms. interrupt driven interval timer (VIA timer #1), and
initializes the clock chip.  Clock initialization makes sure the
clock is running, the chip is not in test mode, and the interrupt
capabilities of the chip are reset.  The chip can interrupt,
however, it is not connected to the system interrupt system and
it's interrupt is ignored.  The interrupt is cleared and reset so
it does not effect the running of the clock.

The Timer driver unitunmount procedure turns off the VIA timer
interrupt for Timer #2.  It also installs in the timer interrupt
vector a pointer to a ReTurn from Exception (RTE) instruction to
insure system integrity in case of a spurrious level 5 interrupt.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Internal :

     1) Table entry ID is the entry number into the table to the
     entry defined.

     2) The VIA clock (phe 2) works at 1 megaherz, therefore, 1
        tick every 1 microsecond.  Consequently, the 16 bit
        counter only counts a maximum of 64K microseconds
        (approx. 65 milliseconds).

     3) MAX_ENTRIES will probably be equal to 10.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Questions :

 1)  How does the user setup and use VIA timer #2 to get access
     to the timer without causing Timer interrupts and making the
     Bell sound?

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Eventually the Timer driver can be used for the time-of-day clock.
The clock should be read with unitreads and set with unitwrites.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

The Timer table Data structure

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

The table has 6 fields.  They are :

     1) Flags - (word)
          bits defined :
            D0 = Valid Entry flag
            D1 = Continuous/One-shot mode flag
            D2 = Skip First Call flag
            D3 = Enable/Disable flag

     2) Address of user's timer service routine - (long word)

     3) Count of interrupt periods - (word)

     4) Down Counter - (word)

     5) Register A4 save area - (long word)

     6) Register A5 save area - (long word)

Explanation of flag states :

     1) Valid Entry flag -  1 = this table entry is valid.  0 =
                            this table entry is not in use
                            (invalid).

     2) Continuous/One-shot mode flag -
                            1 = use entry in One-shot mode.
                            0 = use entry in Continuous mode.

     3) Skip First Call flag 1 = Skip first call to user's
                            service routine.  0 = Call user's
                            service routine everytime.

     4) Enable/Disable flag -
                            1 = entry Disabled.
                            0 = entry Enabled.


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

The Timer Driver pseudo-code design

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

The Interrupt Routine :

Interrupt routine processes the table entries to determine which
user service routine, if any, to call.

TIMER_INTERRUPT :

   for i := 1 to MAX_ENTRIES do
   {if have valid entry that is not disabled then see if should call}
   if (Valid Entry flag[i] is set) AND
      (Enable/Disable flag[i] is clear) then begin
         Down_Counter[i] := Down_Counter[i] - 1;
         if (Down_Counter[i] = 0) then begin  {call user service routine}
            Get register A4 and A5 save values from entry and assign them to the
              registers.
              (A4) := RegA4[i];
              (A5) := RegA5[i];
            Call user's service routine at Address[i].
            if (Continuous/One-shot mode flag is clear) then
               Down_Counter[i] := Counter[i];  {Continuous - restart counter}
            else
               Valid Entry flag[i] := clear; {One-shot - Delete entry}
         end; {2nd if}
     end; {1st if}

   do RTE.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Since do not guarantee that any of the table operations are
indivisible, there is no need to disable the timer interrupt on
the 68000.

Unit I/O interface :


UNITSTATUS :   { do table manipulation & Bell procedures }

   Case (Control) of
     (0) : BELL;
     (1) : CREATE;
     (2) : DELETE;
     (3) : DISABLE;
     (4) : ENABLE;
     Otherwise : ERROR -> Invalid Timer driver function.
   End;
   Return;

(**********************)

CREATE :  {create a timer table entry}

   i := Find table entry that is unused.
          i := 1;  found := false;
          while ( (i<=MAX_ENTRIES) AND (NOT found) ) do begin
            found := NOT Valid Entry flag[i];  {find a entry that is invalid}
            i := i + 1;
           end;
   if (NOT found) then ERROR -> Table full.   {No entries unused in table}
   else begin   {found a free entry}
     get buffer_address.
     paramter_block := buffer_address;
     Address of user routine[i] := @parameter_block[bytes-0,1,2,3];
     Count[i] := @parameter_block[bytes-4,5];
     Down_counter[i] := Count[i];
     Flags[i] := 0; {clear all flags}
     pb_flags := @parameter_block[bytes-6,7];
     if (pb_flags Continuous/One-shot flag is set) then
        set Continuous/One-shot flag[i]; {set entry's flag if set}
     if (pb_flags Skip First Call flag is set) then
        set Skip First Call flag[i];   {set entry's flag if set}
     RegA4[i] := (A4);  {save register contents, needed for Pascal routines}
     RegA5[i] := (A5);  {A4 - jump table address,A5 - global data area}
     set Valid Entry flag[i].
     @parameter_block[bytes-8,9] := i; {return Table entry ID}
   end;
   Return;

(**********************)

DELETE :      {delete a timer table entry}

   get buffer_address.
   paramter_block := buffer_address;
   i := @parameter_block[bytes-0,1];  {get table entry ID}
   if (i is a valid index into the Timer table) then
     clear Valid Entry flag[i].
   else
     ERROR -> Invalid table entry ID.
   Return;

(**********************)

DISABLE :     {disable a timer table entry}

   get buffer_address.
   paramter_block := buffer_address;
   i := @parameter_block[bytes-0,1];  {get table entry ID}
   if (i is a valid index into the Timer table) then
     set Enable/Disable flag[i].
   else
     ERROR -> Invalid table entry ID.
   Return;

(**********************)

ENABLE :      {enable a timer table entry}

   get buffer_address.
   paramter_block := buffer_address;
   i := @parameter_block[bytes-0,1];  {get table entry ID}
   if (i is a valid index into the Timer table) then begin
     Down_Counter[i] := Counter[i]; {restart down counter}
     clear Enable/Disable flag[i].
   end else
     ERROR -> Invalid table entry ID.
   Return;


(**********************)

BELL :  {passed frequency,pattern and duration make a Bell sound}

     get buffer_address.
     paramter_block := buffer_address;

     Initialize VIA for frequency and pattern without disturbing Timer #1.
       Turn-off Shift Register by clearing it to all zeros.
       Set bit D5 of the Auxillary Control Register (ACR) by doing a Logical
         ORI.B operation with the value $10 to the ACR.
       Frequency := @parameter_block[bytes-0,1];
       Set Timer #2 to Frequency.
     Call Create to setup a One-shot mode table entry with Count equal to the
      value of duration(passed in parameter block).
       Shut_off := false;
       Have an Internal parameter block for call to Create.  It has the format :
                ADDR  DATA.L   address of Bell timer service routine
                CNT   DATA.W   store duration-
                               duration := @parameter_block[bytes-4,5];
                FLG   DATA.W   all bits clear except set One-shot mode
                TE    DATA.W   table entry-ignore
       Call Create routine directly, not through Unit I/O.

     Pattern := @parameter_block[byte-2];
     Set Shift Register to Pattern.

     Wait until timer interrupt call.
        while (not Shut_off) do
          if (IFR T2 interrupt bit set) then begin
            clear IFR T2 bit;
            set Timer #2 to Frequency;
          end;

     Shut-off Timer #2 and Shift Register without disturbing Timer #1.
       Clear Shift Register to shut-off speaker
       Clear bit D5 of the ACR by doing a Logical ANDI.B operation with the
        value $EF to the ACR.

     Return.

  Bell timer service routine :

     Shut_off := true;
     do RTS.

&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&

UNITINSTALL :   {initialize Timer table, setup VIA, turnon VIA interrupts}

  Disable ALL interrupts on VIA.
  Initialize Timer table to all unused entries.

    for i := 1 to MAX_ENTRIES do
      Valid Entry flag[i] := clear;

  Setup VIA for Timer #2 to do 50 millisecond interval interrupts.

    Put address of Timer driver interrupt service routine in
    Timer interrupt vector.
    Setup for Timer #2 to run in free-running mode with PB7
    disabled.
    Set timer for 50 milliseconds.
    Clear Interrupt Status register (IFR).

  Enable interrupts on VIA for Timer #2 only.
  Return.

---------------------------------------------------------------------------

possible ASSEMBLY language for UNITINSTALL

;
ACR     EQU     $30F77
ACRBYTE EQU     $40       ;ACR DATA - T1 FREE RUN DISABLE PB7
IER     EQU     $30F7D
DISABL  EQU     $7F       ;DISABLE ALL INTERRUPTS
ENBLT1  EQU     $C0       ;ENABLE IRQ FOR T1
IFR     EQU     $30F7B
CLEAR   EQU     $FF       ;CLEAR ALL INT STAT BITS
T1LL    EQU     $30F6D    ;TIMER 1 LATCH LOW
T1LH    EQU     $30F6F    ;TIMER 1 LATCH HIGH
T1CL    EQU     $30F69    ;TIMER 1 COUNTER LOW - READ ONLY
T1CH    EQU     $30F6B    ;TIMER 1 COUNTER HIGH
TIML    EQU     $00
TIMH    EQU     $20
;
START
;
        MOVE.B  #DISABL,IER.L           ;TURN OFF ALL INTERUPTS ON VIA
;
; INITIALIZE TIMER TABLE
;
       ******* ????
       ******* ????
;
; PUT ADDRESS OF INTERRUPT ROUTINE IN VECTOR
;
        LEA     TIMINT,A0
        MOVE.L       A0,VECTOR.W
;
; SETUP VIA
;
        MOVE.B  #ACRBYTE,ACR.L          ;FREE RUN MODE PB7 OUTPUT DISABLED
        MOVE.B  #TIML,T1LL.L            ;TIMER #1 LATCH LOW
        MOVE.B  #TIMH,T1LH.L            ;TIMER #1 LATCH HIGH
        MOVE.B  #TIMH,T1CH.L            ;TIMER #1 COUNTER HIGH - FORCE LOAD
        MOVE.B  #CLEAR,IFR.L            ;CLEAR IFR
;
; ENABLE TIMER #2
;
        MOVE.B  #ENBLT1,IER.L           ;TURN INTERUPTS ON FOR T1
;
        ANDI.W  #$2BFF,SR    <--+       ;ALLOW LEVEL 4 INTERRUPTS ON 68K
        RTS                     +
        END     START           +____ not sure if necessary

&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&



UNITUNMOUNT :    {turn off VIA timer #2 interrupts}

   Clear Interrupt Enable Register bit for Timer #2 in the VIA.
   Return.

&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&

