/*
** Listing 1.
** Programmers' Forum April 1991
**
** A quick program to demonstrate VBL handling
** in C. The example is trivial, simply flipping
** the background colour of the screen every second.
**
** Compiler system: Lattice C v5.06
** Compile options: Phase 1: -cafku  Phase 2: -ms -v
** Link with C.O, LCG.LIB and LC.LIB
** Written on 10th February 1991
*/

#include <aes.h>
#include <osbind.h>
#include <stdlib.h>
#include <string.h>

/*
** Define some local symbols.
*/

#define UNINSTALLED 0               /* Flags for the variable 'status'         */
#define USED_OLD    1               /*  <- set to this if existing queue used  */
#define USED_NEW    2               /*  <- set to this if we had to create one */

#define INCREMENT   70              /* 1s at 70Hz, change to 50 (UK) or 60 (US)*/
                                    /* if you have a colour monitor            */

#define STOP        0               /* States for the semaphore */
#define GO          1

#define EMPTY       0L              /* Empty slot value */


/*
** Prototype the functions.
*/

int main(int, char **, char **);
void __saveds install(void);
void __saveds remove(void);
void __saveds VBL_handler(void);


/*
** System variable addresses.
*/

#define etv_term        0x0408
#define vblsem          0x0452
#define nvbls           0x0454
#define _vblqueue       0x0456
#define _vbclock        0x0462
#define vdr_colour_0    0xFF8240



/*
** Global variables.
*/

int status = UNINSTALLED;
unsigned long *old_VBL, *new_VBL, *current;
unsigned short save_colour;

unsigned long old_etv_term;

int main(argc,argv,env)

int argc;
char **argv, **env;

{
    appl_init();
    Supexec((void *)install);
    if (status == UNINSTALLED)
        {
        form_alert(1,"[3]|VBL vector list expansion|failed.| |][Abort]");
        return(1);
        }
    form_alert(1,"[2]|VBL demonstration.|Screen should be changing.| |][Quit]");
    Supexec((void *)remove);
    appl_exit();
    return(0);
}


/*
** Function to install the VBL handler: this should
** be called in supervisor mode only.  It finds a free
** slot to install the handler vector in. If this is
** not possible the queue area is expanded to make room.
**
** Usage:   void install(void);
*/

void __saveds install(void)

{
    register int f;
    register unsigned short count;
    register unsigned short *semaphore;

    old_VBL = *(unsigned long **)_vblqueue;
    current = old_VBL;
    count = *(unsigned short *)nvbls;
    semaphore = (unsigned short *)vblsem;

    /* Scan existing list for a free slot */
    for (f=1,current=old_VBL+1; f<count && *current != EMPTY; f++,current++)
        continue;
    if (f == count)
        {
        /* No free slot - must move the list */
        new_VBL = (unsigned long *)malloc(++count * sizeof(void *));
        if (new_VBL == NULL)
            return;     /* Error in memory allocation: quit now */
        memcpy(new_VBL,old_VBL,--count * sizeof(void *));
        current = new_VBL + count++;
        status = USED_NEW;
        }
    else 
        {
        /* Just write our handler's address into the empty slot */
        status = USED_OLD;
        new_VBL = old_VBL;
        }

/*    old_etv_term = *(unsigned long *)etv_term;            */
/*    *(unsigned long *)etv_term = (unsigned long)remove;   */

    *current = (unsigned long)VBL_handler;
    *semaphore = STOP;
    *(unsigned long **)_vblqueue = new_VBL;
    *(unsigned short *)nvbls = count;
    *semaphore = GO;
    save_colour = *(unsigned short *)vdr_colour_0;
}


/*
** Function to remove the VBL handler from the
** queue, restoring the queue area to its old
** position if it had to be moved. Call from
** supervisor mode.
**
** Usage:   void remove(void);
*/

void __saveds remove(void)

{
    register unsigned short *semaphore, *wptr;

    semaphore = (unsigned short *)vblsem;
    wptr = (unsigned short *)nvbls;
    *semaphore = STOP;
    *current = EMPTY;

/*    *(unsigned long *)etv_term = old_etv_term;       */

    if (status == USED_NEW)
        {
        /* If list moved, set up the system to use the old list again */
        (*wptr)--;
        *(unsigned long **)_vblqueue = old_VBL;
        free(new_VBL);
        }
    *semaphore = GO;
    /* Ensure the screen is restored to the state it was before the program */
    *(unsigned short *)vdr_colour_0 = save_colour;
}


/*
** Function to actually do something when invoked
** by an interrupt. In this case it simply flips
** the screen colour periodically. The system
** will call this in supervisor mode, but there
** almost are no restrictions on our register
** usage.
*/

void __saveds VBL_handler(void)

{
    register unsigned short *video;
    register unsigned long ticker;
    static unsigned long next;

    ticker = *(unsigned long *)_vbclock;
    if (ticker < next)
        return;
    next = ticker + INCREMENT;
    video = (unsigned short *)vdr_colour_0;
    *video ^= 0x01;             /* For colour, change 0x01 to 0x77 */
}
