(c)  Copyright 1989 Commodore-Amiga, Inc.   All rights reserved.
The information contained herein is subject to change without notice, and 
is provided "as is" without warranty of any kind, either expressed or implied.  
The entire risk as to the use of this information is assumed by the user.



                SoftInt MsgPorts and the Timer Device

                     Carolyn Scheppner - CATS


     
Most Amiga programmers are familiar with the PA_SIGNAL type of
Exec MsgPort which signals a task when a message is received.  This
is the type of MsgPort that Intuition provides for IDCMP, and also
the type commonly used for Exec device IO.  The amiga.lib functions
CreatePort() and DeletePort() are for PA_SIGNAL ports.

A less familiar type of Exec MsgPort is the PA_SOFTINT port.
PA_SOFTINT ports cause a software interrupt when a message is received, 
rather than signalling a task.  Software interrupts execute at a priority 
lower than hardware interrupts but higher than tasks - that is, a software 
interrupt will pre-empt even a running task.  

For time-sensitive applications the timer.device will give more precise
timings with a PA_SOFTINT port than with a PA_SIGNAL port.  This is because 
the PA_SIGNAL port typically requires applications to Wait() for the device 
to respond either directly, or indirectly with a DoIO() call.  Such a Wait()
will switch out the calling task until the device has responded at which 
time the calling task will be placed in the ready queue awaiting its turn
to run.  Under heavy multi-tasking loads, this extra overhead can cause
additional delay in interval timings leading many people to believe that
the timer.device is not accurate.  In reality, the timer.device can provide
accurate timing even with intervals occuring several times per video
frame.  The trick is to eliminate the task-switching overhead by using a
PA_SOFTINT port.

This can provide timings which are more consistent under heavy multi-tasking 
loads.  This technique could be useful for an application that needs to poll 
or control external hardware at regular intervals, and can also be used for 
simple incrementing of counters or program timers.  

Keep in mind that your software interrupt code should be kept as short and 
quick as possible, so that system and application tasks can get the time 
they need.  Note that if a Disable() is in effect, no interrupts will be 
generated.  If a Forbid() is in effect interrupts will still run normally. 

The audio chapter of Addison-Wesley's "ROM Kernel Manual: Libraries 
and Devices" gives an audio device example which demonstrates the use of
a PA_SOFTINT port for the automatic manipulation of audio samples.
The example listed here, TimerSoftInt.c, demonstrates how a PA_SOFTINT
port can be used with the timer.device to produce software interrupts
several times per frame.  TimerSoftInt changes the screen color to display 
when and how often the timer software interrupt code is being executed.
By adjusting the value passed to TimerSoftInt, you can find an interval
which will cause the timer interrupts to occur at approximately the
same vertical position each frame, allowing you to clearly see and test
the consistency of the timing under different multi-tasking loads such as
when running a compiler, for example.

  
IMPORTANT

Do not BeginIO() to any device other than timer or audio from within a 
software or hardware interrupt.  The BeginIO() code of other devices may 
allocate memory, Wait(), or perform other functions which are illegal or 
dangerous during interrupts.  

The example below directly accesses the background color register so you
can "see" when a timer interrupt occurs.  An application should NEVER do
this.  Use direct access to Amiga hardware registers for debugging or 
demonstration purposes ONLY, as is the case here.


--------------------------------------------------------------------------
/*
 * TimerSoftInt.c  -  C. Scheppner  C.A.T.S.  02/89
 * Copyright 1989 Commodore-Amiga, Inc.   All Rights Reserved
 *
 * Demonstrates use of the timer.device with a PA_SOFTINT MsgPort
 * for applications that need to do processing on a regular basis,
 * several times per frame, in a multi-tasking environment.
 */

#include "exec/types.h"
#include "exec/interrupts.h"
#include "exec/memory.h"
#include "devices/timer.h"
#include "libraries/dos.h"
#include "hardware/custom.h"

extern struct Custom custom;

#define RED_CT     18
#define MIC_DELAY  2500
#define MIN_DELAY  1200

extern VOID *tsoftcode();  /* our timer softint code */

struct Interrupt    *tint = NULL;
struct timerequest  *treq = NULL;
struct MsgPort      *tport = NULL;

BOOL OpenedTimer = FALSE;

/* Global variables shared with the softint code
 * If our tsoftcode was in assembler, we could use is_Data
 * pointer instead to tell softint code where shared data is
 */
#define OFF 0
#define ON  1
#define STOPPED 2

ULONG redct, micdelay;

BOOL SFlag=OFF;

char tportname[] = "CAS_CBM_timer_softint";
char cprt[] =
  "Copyright (c) 1989 Commodore-Amiga,Inc. All Rights Reserved";


main(argc,argv)
int  argc;
char **argv;
   {
   if(!argc)  exit(RETURN_FAIL);  /* CLI only example...Uses printf */

   redct = RED_CT;
   micdelay = MIC_DELAY;
   if(argc>1)
      {
      if(argv[1][0]=='?') cleanexit("TimerSoftInt [mdelay]",RETURN_OK);
      else micdelay=atoi(argv[1]);
      if(micdelay < MIN_DELAY)
         {
         micdelay = MIN_DELAY;
         printf("Delay set to example minimum of %ld\n",MIN_DELAY);
         }
      }

   /* Allocate our MsgPort and Interrupt structures
    * Since we can't use CreatePort, we'll build the port ourselves.
    * Create Port creates a PA_SIGNAL and allocs a signal.
    * We want a PA_SOFTINT port;
    */

   if(!(tport = (struct MsgPort *)
       AllocMem(sizeof(struct MsgPort),MEMF_PUBLIC|MEMF_CLEAR)))
           cleanexit("Can't allocmem msgport",RETURN_FAIL);

   if(!(tint = (struct Interrupt *)
       AllocMem(sizeof(struct Interrupt),MEMF_PUBLIC|MEMF_CLEAR)))
           cleanexit("Can't allocmem interrupt",RETURN_FAIL);


   /* Set up the interrupt structure.
    * Note that we are priority 0.  Software interrupts may only be
    * priority -32, -16, 0, +16, or +32.  Also note that the
    * correct node type for a software interrupt is NT_INTERRUPT.
    * (NT_SOFTINT is an internal flag of Exec's)
    */
   tint->is_Code = (VOID (*)())tsoftcode;  /* Our softint routine */
   tint->is_Node.ln_Type = NT_INTERRUPT;
   tint->is_Node.ln_Pri = 0;

   /* Set up the PA_SOFTINT msgport */
   tport->mp_Node.ln_Type = NT_MSGPORT;
   tport->mp_Node.ln_Name = (char *)tportname;
   tport->mp_Flags = PA_SOFTINT;
   tport->mp_SigTask = (struct Task *)tint;  /* Ptr to interrupt struct */

   /* Not using CreatePort, so we must add the port ourselves */
   AddPort(tport);

   /* Now Create the IO request */
   if(!(treq=(struct timerequest *)
      CreateExtIO(tport,sizeof(struct timerequest))))
         cleanexit("Can't create ioreq", RETURN_FAIL);

   /* Open the timer device - Note 0 return means success */
   if(OpenDevice("timer.device",UNIT_MICROHZ,treq,0))
      cleanexit("Can't open timer device",RETURN_FAIL);

   OpenedTimer = TRUE;    /* Flag for closing in cleanup */

   /* Now, let's do something with it */
   SFlag = ON;
   begintr(treq);         /* Prime the pump with first timer request */

   printf("Started timer softint.  Press CTRL/C to exit...");
   Wait(SIGBREAKF_CTRL_C);
   printf("\nReceived CTRL_C... Turning off softint\n");
   
   SFlag = OFF;
   while(SFlag != STOPPED)  Delay(10);

   cleanup();
   exit(RETURN_OK);
   }


/* Routine called as software interrupt */
VOID *tsoftcode()
   {
   struct timerequest *tr;
   ULONG de;

   /* Remove our message from our port */
   tr = (struct timerequest *)GetMsg(tport);

   /* If main hasn't flagged us to stop, keep the ball rolling */
   if((tr)&&(SFlag==ON))
      {
      /* Hit background color register for demonstration purposes
       * and send the timer request out again.
       * IMPORTANT: This self-perpetuating technique of calling
       * BeginIO during a software interrupt may only be used with
       * the audio and timer device.
       */
      custom.color[0] = 0x0FF0;
      begintr(tr);

      /* Then hit the background color register again to show the
       * the duration of the rest of our softint routine (for/next).
       * Hitting registers like this is for debugging only!
       */
      custom.color[0] = 0x0F00;
      for(de=0; de < redct; de++);  /* Our do-nothing softint routine */

      /* Back to default screen color */
      custom.color[0] = 0x005A;
      }

   /* Else flag main we have indeed stopped */
   else(SFlag=STOPPED);
   return(0);
   }


/* begintr(tr)
 * Sets up and sends off timer request.
 * IMPORTANT: Do not BeginIO to any device other than timer or audio
 * from within a software or hardware interrupt.  The BeginIO code
 * of other devices may allocate memory, wait, or perform other
 * functions which are illegal or dangerous during interrupts.
 */
begintr(tr)
struct timerequest *tr;
   {
   /* Set up the timer command */
   tr->tr_node.io_Command = TR_ADDREQUEST;
   tr->tr_time.tv_micro = micdelay;
   BeginIO(tr);
   }

/* Prints message if any, cleans up, and exits */
cleanexit(s,n)
UBYTE *s;
int n;
   {
   if(*s)  printf("%s\n",s);
   cleanup();
   exit(n);
   }

/* Close/deallocate everything opened/allocated */
cleanup()
   {
   if(OpenedTimer)  CloseDevice(treq);
   if(treq)         DeleteExtIO(treq);
   if(tport)
      {
      RemPort(tport);
      FreeMem(tport,sizeof(struct timerequest));
      }
   if(tint)  FreeMem(tint, sizeof(struct Interrupt));
   }


atoi( s )
char *s;
   {
   int num = 0;
   int neg = 0;

   if( *s == '+' ) s++;
   else if( *s == '-' )
      {
      neg = 1;
      s++;
      }

   while( *s >= '0' && *s <= '9' )
      {
      num = num * 10 + *s++ - '0';
      }
   if( neg ) return( - num );
   return( num );
   }


/* end */



