Copyright (c) Hyperion Entertainment and contributors.

CIA Resource

From AmigaOS Documentation Wiki
Jump to navigation Jump to search
Codereview.png Code samples on this page are not yet updated to AmigaOS 4.x some of them may be obsolete or incompatible with AmigaOS 4.x.

CIA Resource

The CIA resource provides access to the timers and timer interrupt bits of the 8520 Complex Interface Adapter (CIA) A and B chips. This resource is intended for use by high performance timing applications such as MIDI time stamping and SMPTE time coding.

Four functions allow you to interact with the CIA hardware.

CIA Resource Functions

AbleICR() Enable or disable Interrupt Control Register interrupts. Can also return the current or previous enabled interrupt mask.
AddICRVector() Allocate one of the CIA timers by assigning an interrupt handler to an interrupt bit and enabling the interrupt of one of the timers. If the timer you request is not available, a pointer to the interrupt structure that owns it will be returned.
RemICRVector() Remove an interrupt handler from an interrupt bit and disable the interrupt.
SetICR() Cause or clear one or more interrupts, or return the current or previous interrupt status.

Each CIA chip has two interval timers within it—Timer A and Timer B—that may be available. The CIA chips operate at different interrupt levels with the CIA-A timers at interrupt level 2 and the CIA-B timers at interrupt level 6.

Choose A Timer Wisely.
The timer you use should be based solely on interrupt level and availability. If the timer you request is not available, try for another. Whatever you do, do not base your decision on what you think the timer is used for by the system.

You allocate a timer by calling AddICRVector(). This is the only way you should access a timer. If the function returns zero, you have successfully allocated that timer. If it is unavailable, the owner interrupt will be returned.

/* allocate CIA-A Timer A */
inta = AddICRVector (CIAResource, CIAICRB_TA, &tint);

if (inta)  /* if allocate was not successful */
    printf("Error: Could not allocate timer\n");
else
    {
    ...ready for timing
    }

The timer is deallocated by calling RemICRVector(). This is the only way you should deallocate a timer.

RemICRVector(CIAResource, CIAICRB_TA, &tint);

Your application should not make any assumptions regarding which interval timers (if any) are available for use; other tasks or critical operating system routines may be using the interval timers. In fact, in the latest version of the operating system, the timer device may dynamically allocate one of the interval timers.

Time Is Of The Essence!
There are a limited number of free CIA interval timers. Applications which use the interval timers may not be able to run at the same time if all interval timers are in use. As a general rule, you should use the timer device for most interval timing.

You read from and write to the CIA interrupt control registers using SetICR() and AbleICR(). SetICR() is useful for sampling which cia interrupts (if any) are active. It can also be used to clear and generate interrupts. AbleICR() is used to disable and enable a particular CIA interrupt. Additional information about these functions can be found in the SDK.

Things to keep in mind:

  1. Never directly read from or write to the CIA interrupt control registers. Always use SetICR() and AbleICR().

  2. Your interrupt routine will be called with a pointer to your data area in register A1, and a pointer to the code being called in register A5. No other registers are set up for you. You must observe the standard convention of preserving all registers except D0–D1 and A0–A1.

  3. Never turn off all level 2 or level 6 interrupts. The proper way to disable interrupts for an interval timer that you’ve successfully allocated is via the AbleICR() function.

  4. Interrupt handling code should be written in assembly code and, if possible, should signal a task to do most of the work.

  5. Do not make assumptions about which CIA interval timers (if any) are available for use. The only proper way to own an interval timer is via the AddICRVector() function.

  6. Do not use SetICR(), AbleICR() and RemICRVector() to affect timers or other CIA hardware which your task does not own.

Changes in the CIA resource:

  • In pre-V36 versions of the operating system, SetICR() could return FALSE for a particular interrupt just prior to processing the interrupt. SetICR() now returns TRUE for a particular interrupt until sometime after the interrupt has been processed.
  • Applications which only need to read a CIA interval timer should use the ReadEClock() function of the timer device. See Timer Device for more information on ReadEClock().
  • The timer device may dynamically allocate a free CIA interval timer. Do not make any assumptions regarding which interval timers are in use unless you are taking over the machine completely.
/*
 * Cia_Interval.c
 *
 * Demonstrate allocation and use of a cia interval timer
 *
 * Compile with SAS C 5.10  lc -b1 -cfistq -v -y -L
 *
 * Run from CLI only
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/tasks.h>
#include <exec/interrupts.h>
#include <hardware/cia.h>
#include <resources/cia.h>

#include <clib/exec_protos.h>
#include <clib/cia_protos.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>


/* prototypes */

void    StartTimer      (struct freetimer *ft, struct exampledata *ed);
int     FindFreeTimer   (struct freetimer *ft, int preferA);
int     TryTimer        (struct freetimer *ft);
void    main            ( USHORT, char **);


/* see usage of these defines in StartTimer() below */

#define COUNTDOWN 20
#define HICOUNT 0xFF
#define LOCOUNT 0xFF

#define STOPA_AND  CIACRAF_TODIN |CIACRAF_PBON | CIACRAF_OUTMODE | CIACRAF_SPMODE

        /*
        ;
        ; AND mask for use with control register A
        ; (interval timer A on either CIA)
        ;
        ; STOP -
        ;       START bit 0 == 0 (STOP IMMEDIATELY)
        ;       PBON  bit 1 == same
        ;       OUT   bit 2 == same
        ;       RUN   bit 3 == 0 (SET CONTINUOUS MODE)
        ;       LOAD  bit 4 == 0 (NO FORCE LOAD)
        ;       IN    bit 5 == 0 (COUNTS 02 PULSES)
        ;       SP    bit 6 == same
        ;       TODIN bit 7 == same (unused on ciacra)

        */

#define STOPB_AND  CIACRBF_ALARM | CIACRBF_PBON | CIACRBF_OUTMODE

        /*
        ;
        ; AND mask for use with control register B
        ; (interval timer B on either CIA)
        ;
        ; STOP -
        ;       START bit 0 == 0 (STOP IMMEDIATELY)
        ;       PBON  bit 1 == same
        ;       OUT   bit 2 == same
        ;       RUN   bit 3 == 0 (SET CONTINUOUS MODE)
        ;       LOAD  bit 4 == 0 (NO FORCE LOAD)
        ;       IN0   bit 5 == 0 (COUNTS 02 PULSES)
        ;       IN1   bit 6 == 0 (COUNTS 02 PULSES)
        ;       ALARM bit 7 == same (TOD alarm control bit)

        */

#define STARTA_OR  CIACRAF_START

        /*
        ;
        ; OR mask for use with control register A
        ; (interval timer A on either CIA)
        ;
        ; START -
        ;
        ;       START bit 0 == 1 (START TIMER)
        ;
        ;       All other bits unaffected.
        ;

        */

#define STARTB_OR  CIACRBF_START

        /*
        ;
        ; OR mask for use with control register B
        ; (interval timer A on either CIA)
        ;
        ; START -
        ;
        ;       START bit 0 == 1 (START TIMER)
        ;
        ;       All other bits unaffected.
        ;

        */


/*
 * Structure which will be used to hold all relevant information about
 * the cia timer we manage to allocate.
 *
 */

struct freetimer
{
    struct Library *ciabase;        /* CIA Library Base             */
    ULONG  timerbit;                /* timer bit allocated          */
    struct CIA *cia;                /* ptr to hardware              */
    UBYTE *ciacr;                   /* ptr to control register      */
    UBYTE *cialo;                   /* ptr to low byte of timer     */
    UBYTE *ciahi;                   /* ptr to high byte of timer    */
    struct Interrupt timerint;      /* Interrupt structure          */
    UBYTE  stopmask;                /* Stop/set-up timer            */
    UBYTE  startmask;               /* Start timer                  */
};

/*
 * Structure which will be used by the interrupt routine called
 * when our cia interval timer generates an interrupt.
 *
 */

struct exampledata
{
    struct Task *task;      /* task to signal */
    ULONG   signal;         /* Signal bit to use */
    ULONG   counter;
};


struct CIA *ciaa = (struct CIA *)0xbfe001;
struct CIA *ciab = (struct CIA *)0xbfd000;


#ifdef LATTICE
int CXBRK(void) { return(0); }  /* Disable SAS CTRL/C handling */
int chkabort(void) { return(0); }  /* really */
#endif

/*
 * This is the interrupt routine which will be called when our CIA
 * interval timer counts down.
 *
 * This example decrements a counter each time the interrupt routine
 * is called until the counter reaches 0, at which time it signals
 * our main task.
 *
 * Note that interrupt handling code should be efficient, and will
 * generally be written in assembly code.  Signaling another task
 * such as this example does is also a useful way of handling
 * interrupts in an expedient manner.
 */

void __asm ExampleInterrupt(register __a1 struct exampledata *ed)
{
if (ed->counter)
    {
    ed->counter--;                  /* decrement counter */
    }
else
    {
    ed->counter = COUNTDOWN;        /* reset counter     */

    Signal(ed->task,(1L << ed->signal));
    }
}


/***********************************
 *  main()
 ***********************************/

void main(USHORT argc,char **argv)
{
struct freetimer ft;
struct exampledata ed;

/* Set up data which will be passed to interrupt */

ed.task = FindTask(0L);

if (ed.signal = AllocSignal(-1L))
    {
    /* Prepare freetimer structure : set-up interrupt */

    ft.timerint.is_Node.ln_Type = NT_INTERRUPT;
    ft.timerint.is_Node.ln_Pri  = 0;
    ft.timerint.is_Node.ln_Name = "cia_example";

    ft.timerint.is_Data         = (APTR)&ed;
    ft.timerint.is_Code         = (APTR)ExampleInterrupt;

    /* Call function to find a free CIA interval timer
     * with flag indicating that we prefer a CIA-A timer.
     */

    printf("Attempting to allocate a free timer\n");

    if (FindFreeTimer(&ft,TRUE))
        {
        if (ft.cia == ciaa)
            {
            printf("CIA-A timer ");
            }
        else
            {
            printf("CIA-B timer ");
            }

        if (ft.timerbit == CIAICRB_TA)
            {
            printf("A allocated\n");
            }
        else
            {
            printf("B allocated\n");
            }


        /* We found a free interval timer.  Let's start it running. */

        StartTimer(&ft,&ed);

        /* Wait for a signal */

        printf("Waiting for signal bit %ld\n",ed.signal);

        Wait(1L<<ed.signal);

        printf("We woke up!\n");

        /* Release the interval timer */

        RemICRVector(ft.ciabase,ft.timerbit,&ft.timerint);

        }
    else
        {
        printf("No CIA interval timer available\n");
        }

    FreeSignal(ed.signal);
    }
}


/*
 * This routine sets up the interval timer we allocated with
 * AddICRVector().  Note that we may have already received one, or
 * more interrupts from our timer.  Make no assumptions about the
 * initial state of any of the hardware registers we will be using.
 *
 */

void StartTimer(struct freetimer *ft, struct exampledata *ed)
{
register struct CIA *cia;

cia = ft->cia;

/* Note that there are differences between control register A,
 * and B on each CIA (e.g., the TOD alarm bit, and INMODE bits.
 */

if (ft->timerbit == CIAICRB_TA)
    {
    ft->ciacr = &cia->ciacra;       /* control register A   */
    ft->cialo = &cia->ciatalo;      /* low byte counter     */
    ft->ciahi = &cia->ciatahi;      /* high byte counter    */

    ft->stopmask = STOPA_AND;       /* set-up mask values   */
    ft->startmask = STARTA_OR;
    }
else
    {
    ft->ciacr = &cia->ciacrb;       /* control register B   */
    ft->cialo = &cia->ciatblo;      /* low byte counter     */
    ft->ciahi = &cia->ciatbhi;      /* high byte counter    */

    ft->stopmask = STOPB_AND;       /* set-up mask values   */
    ft->startmask = STARTB_OR;
    }


/* Modify control register within Disable().  This is done to avoid
 * race conditions since our compiler may generate code such as:
 *
 *      value = Read hardware byte
 *      AND  value with MASK
 *      Write value to hardware byte
 *
 * If we take a task switch in the middle of this sequence, two tasks
 * trying to modify the same register could trash each others' bits.
 *
 * Normally this code would be written in assembly language using atomic
 * instructions so that the Disable() would not be needed.
 */


Disable();

/* STOP timer, set 02 pulse count-down mode, set continuous mode */

*ft->ciacr &= ft->stopmask;
Enable();

/* Clear signal bit - interrupt will signal us later */
SetSignal(0L,1L<<ed->signal);

/* Count-down X # of times */
ed->counter = COUNTDOWN;

/* Start the interval timer - we will start the counter after
 * writing the low, and high byte counter values
 */

*ft->cialo = LOCOUNT;
*ft->ciahi = HICOUNT;

/* Turn on start bit - same bit for both A, and B control regs  */

Disable();
*ft->ciacr |= ft->startmask;

Enable();
}


/*
 * A routine to find a free interval timer.
 *
 * This routine makes no assumptions about which interval timers
 * (if any) are available for use.  Currently there are two interval
 * timers per CIA chip.
 *
 * Because CIA usage may change in the future, your code should use
 * a routine like this to find a free interval timer.
 *
 * Note that the routine takes a preference flag (which is used to
 * to indicate that you would prefer an interval timer on CIA-A).
 * If the flag is FALSE, it means that you would prefer an interval
 * timer on CIA-B.
 *
 */

FindFreeTimer(struct freetimer *ft, int preferA)
{
struct CIABase *ciaabase, *ciabbase;

/* get pointers to both resource bases */

ciaabase = OpenResource(CIAANAME);
ciabbase = OpenResource(CIABNAME);

/* try for a CIA-A timer first ? */

if (preferA)
    {
    ft->ciabase = ciaabase; /* library address  */
    ft->cia     = ciaa;     /* hardware address */
    }
else
    {
    ft->ciabase = ciabbase; /* library address  */
    ft->cia     = ciab;     /* hardware address */
    }

if (TryTimer(ft))
    return(TRUE);

/* try for an interval timer on the other cia */

if (!(preferA))
    {
    ft->ciabase = ciaabase; /* library address  */
    ft->cia     = ciaa;     /* hardware address */
    }
else
    {
    ft->ciabase = ciabbase; /* library address  */
    ft->cia     = ciab;     /* hardware address */
    }

if (TryTimer(ft))
    return(TRUE);

return(FALSE);

}


/*
 * Try to obtain a free interval timer on a CIA.
 */

TryTimer(struct freetimer *ft)
{

if (!(AddICRVector(ft->ciabase,CIAICRB_TA,&ft->timerint)))
    {
    ft->timerbit = CIAICRB_TA;
    return(TRUE);
    }

if (!(AddICRVector(ft->ciabase,CIAICRB_TB,&ft->timerint)))
    {
    ft->timerbit = CIAICRB_TB;
    return(TRUE);
    }

return(FALSE);
}

Additional programming information on the CIA resource can be found in the include files and the Autodocs for the CIA resource and the 8520 spec. The includes files and Autodocs are in the SDK and the 8520 spec is in the Amiga Hardware Reference Manual.

Includes
resources/cia.h
hardware/cia.h
AutoDocs
cia.doc
Hardware
8520 specification