/*
	Speccylator 1.0

	Richard Carlsson 1993-1995

*/

#include "defs.h"


struct IntuitionBase *IntuitionBase;
struct GfxBase *GfxBase;

struct Task *z80TaskPtr;
struct Task *ctrlTaskPtr;

struct Interrupt *VBInterrupt;
struct Remember *rememberKey;

ULONG backWinSig, specWinSig, ackSig, resumeSig, updateSig, audioSig;


struct Screen	*emulScreen;
struct Window	*backWin, *specWin;
struct ViewPort *screenVP;
struct UCopList *bwCopList;

struct BitMap	bwBitMap;
struct RastPort bwRPort;


struct IOAudio *AIOptr1;
struct IOAudio *AIOptr2;
struct MsgPort *audioPort;

ULONG audioOpen = 1;	/* zero if audio device is open */
short sentIO1, sentIO2; /* set if corresponding request is sent */
UBYTE chan1, chan2;	/* separated channel flags */

#define SOUNDMEM_SIZE 2
BYTE *soundMem;


BYTE *z80Mem;
BYTE *attrMem;
WORD *cacheMem;

struct Z80_Control *Zctrl;


char *snapshot;

BYTE *loadBuf;
LONG loadBufSize;


short verify;

#define VERIFY_MENU 1
#define VERIFY_REQ 2


short titleBar, topLine;


int debug;

int (* cleanup_fp)(void);


char memory_msg[] = "Not enough memory.";
char sigalloc_msg[] = "Signal allocation error.";



#include "init.c"

#include "copper.c"

#include "gfx.c"

#include "intuiloop.c"



/*
	Emulation halt/resume functions

*/

void haltEmul(void)
{
    Z80_EXITreq(Zctrl);
    Wait(ackSig);
}


void resumeEmul(void)
{
    Signal(z80TaskPtr, resumeSig);
}



/*
	Verification on/off functions

	The screen updating is turned off while verifications are
	disabled, so system requesters are not overwritten.
*/

void verifiesOff(void)
{
    /*	Turn off screen updating (if the interrupt server exists)
    */
    if (VBInterrupt != NULL) {
	intControl.UPDtype = 3;
	Wait(updateSig);
    }

    /*	Remove verification flags (if the window exists)
    */
    if (specWin != NULL)
	ModifyIDCMP(specWin, specWinIDCMP & ~specWinIDCMPverf);
}


void verifiesOn(void)
{
    /*	Turn screen updating back on, completely redrawing the bitplanes
    */
    intControl.UPDtype = 2;

    /*	Set verification flags (i.e. set all the standard flags)
    */
    if (specWin != NULL) ModifyIDCMP(specWin, specWinIDCMP);
}



/*
	Puts: String + newline output function (standard C 'puts').

	Writes to the standard error stream, or if that does not exist,
	to the standard output stream (if it exists).
*/
void Puts(char *s)
{
    BPTR f;
    struct Process *p = (struct Process *) FindTask(NULL);

    if ((f = p->pr_CES) == NULL) f = p->pr_COS;

    if (p != NULL) {
	FPuts(f, s);
	FPutC(f, '\n');
    }
}



/*
	cleanExit: Cleanup-and-exit function.

	This function can be called both from the control process
	and from the emulation process.
*/
void cleanExit(char *msg, int exitStatus)
{
    if (msg != NULL) Puts(msg);

    /* Call the special process cleanup function.
       The loaded process returns nonzero, and proceeds to exit().
       If zero is returned, the process will be blocked.
    */
    if (!(* cleanup_fp)()) {
	Wait(0);  /* didn't remove itself, and can't call exit() */
    }

    /* only the loaded process may do the following */

    /* close libraries */
    if (IntuitionBase) CloseLibrary(IntuitionBase);
    if (GfxBase) CloseLibrary((struct Library *)GfxBase);

    exit(exitStatus);
}



/*
	allocMemory: Does most of the necessary memory allocation.

*/
void allocMemory(void)
{
    /* independent */
    ULONG size;

    size = Z80_MEMSIZE + SOFTUPD_BUFSIZE;
    if ((z80Mem = (BYTE *) AllocRemember(&rememberKey, size,
	MEMF_ANY | MEMF_CLEAR | MEMF_PUBLIC )) == NULL) goto error;

    if ((cacheMem = (WORD *) AllocRemember(&rememberKey, Z80_CACHESIZE,
	MEMF_ANY | MEMF_PUBLIC )) == NULL) goto error;

    if ((attrMem = (BYTE *) AllocRemember(&rememberKey, Z80_ATTRMEMSIZE,
	MEMF_ANY | MEMF_CLEAR | MEMF_PUBLIC )) == NULL) goto error;

    if ((Zctrl = (struct Z80_Control *) AllocRemember(&rememberKey,
	sizeof(struct Z80_Control), MEMF_ANY | MEMF_PUBLIC )) == NULL)
	    goto error;

    if ((soundMem = (BYTE *) AllocRemember(&rememberKey, SOUNDMEM_SIZE,
	MEMF_CHIP | MEMF_PUBLIC | MEMF_CLEAR )) == NULL) goto error;

    return;

error:
    cleanExit(memory_msg, RETURN_ERROR);
}



/*
	loadROM: ZX Spectrum ROM file loader.

*/
void loadROM(void)
{
    BPTR f;
    int r;

    verifiesOff();
    if ((f = Open(SP_ROMNAME, MODE_OLDFILE)) == NULL)
	cleanExit("couldn't open file " SP_ROMNAME, RETURN_ERROR);
    r = Read(f, z80Mem + Z80_0_OFFSET, SP_ROMSIZE);
    Close(f);
    verifiesOn();

    if (r != SP_ROMSIZE)
	cleanExit("error reading ROM file", RETURN_ERROR);
}



/*
	loadSnapshot: Snapshot file loader.

	Returns nonzero if loading failed, zero otherwise.
	Can only be called after Z80_init().
*/
int loadSnapshot(char *s)
{
    BPTR f;
    int v = 0;

    loadBufSize = 8*1024;

    if ((loadBuf = (BYTE *) AllocMem(loadBufSize, MEMF_ANY)) == NULL)
	cleanExit(memory_msg, RETURN_ERROR);

    verifiesOff();

    if ((f = Open(s, MODE_OLDFILE)) == NULL) {
	Puts("Error opening snapshot file.");
	goto end_error;
    }

    if (27 != Read(f, loadBuf, 27)) goto read_error;

    Zctrl->CPU.I = loadBuf[0];

    Zctrl->CPU.alt_HL.b.L = loadBuf[1];
    Zctrl->CPU.alt_HL.b.H = loadBuf[2];
    Zctrl->CPU.alt_DE.b.E = loadBuf[3];
    Zctrl->CPU.alt_DE.b.D = loadBuf[4];
    Zctrl->CPU.alt_BC.b.C = loadBuf[5];
    Zctrl->CPU.alt_BC.b.B = loadBuf[6];
    Zctrl->CPU.alt_AF.b.F = loadBuf[7];
    Zctrl->CPU.alt_AF.b.A = loadBuf[8];

    Zctrl->CPU.HL.b.L = loadBuf[9];
    Zctrl->CPU.HL.b.H = loadBuf[10];
    Zctrl->CPU.DE.b.E = loadBuf[11];
    Zctrl->CPU.DE.b.D = loadBuf[12];
    Zctrl->CPU.BC.b.C = loadBuf[13];
    Zctrl->CPU.BC.b.B = loadBuf[14];

    Zctrl->CPU.IY.b.L = loadBuf[15];
    Zctrl->CPU.IY.b.H = loadBuf[16];
    Zctrl->CPU.IX.b.L = loadBuf[17];
    Zctrl->CPU.IX.b.H = loadBuf[18];

    Zctrl->CPU.IFF = ((UBYTE)loadBuf[19] & 4 ? 0xC0 : 0);
    Zctrl->CPU.R = loadBuf[20];

    Zctrl->CPU.AF.b.F = loadBuf[21];
    Zctrl->CPU.AF.b.A = loadBuf[22];

    Zctrl->CPU.SP = ((UBYTE)loadBuf[24] << 8) + (UBYTE)loadBuf[23];

    switch ((UBYTE)loadBuf[25]) {
	case 0:
	    Zctrl->CPU.IMF = 0; /* IM 0 <=> IMF = 00 */
	    break;
	case 1:
	    Zctrl->CPU.IMF = 2; /* IM 1 <=> IMF = 10 */
	    break;
	case 2:
	    Zctrl->CPU.IMF = 3; /* IM 2 <=> IMF = 11 */
	    break;
	default:
	    Zctrl->CPU.IMF = 0; /* default to IM 0 */
    }

    /* (loadBuf[26] contains the border colour in bits 0-2) */

    {
	int i;
	for (i=0; i<6; i++) {
	    if (Read(f, loadBuf, loadBufSize) != loadBufSize)
		goto read_error;
	    Z80_WriteBlock(Zctrl, loadBuf, 16384+i*loadBufSize,
		loadBufSize);
	}
    }


    /* The effect of the following is like that of performing a
       RETN instruction. (IFF1 is set equal to IFF2 above).
    */
    Zctrl->CPU.PC = Z80_GetWordLH(Zctrl, Zctrl->CPU.SP);
    Zctrl->CPU.SP += 2;

end:
    if (f != NULL) Close(f);

    verifiesOn();

    FreeMem(loadBuf, loadBufSize);
    loadBuf = 0;

    return v;


read_error:
    Puts("Error reading snapshot file.");

end_error:
    v = 1;
    goto end;
}



/*
	setupScreen: opening emulator screen and windows.

*/

void setupScreen(void)
{
    if ((emulScreen = (struct Screen *) OpenScreenTagList(NULL, screenTags))
	== NULL) cleanExit("couldn't open screen", RETURN_ERROR);

    titleBar = emulScreen->BarHeight + 1;
    topLine = (emulScreen->Height - SPSCR_HEIGHT) >> 1;

    screenVP = &(emulScreen->ViewPort);


    backWinTags[0].ti_Data = (ULONG)emulScreen;
    backWinTags[2].ti_Data = titleBar;
    backWinTags[3].ti_Data = emulScreen->Width;
    backWinTags[4].ti_Data = emulScreen->Height;

    if ((backWin = (struct Window *) OpenWindowTagList(NULL, backWinTags))
	== NULL) cleanExit("couldn't open window", RETURN_ERROR);

    specWinTags[0].ti_Data = (ULONG)emulScreen;
    /* The left edge must pass through bit 15 of even addresses only.
       Rasters are always an even number of bytes wide, so the
       line modulo will automatically be even. */
    specWinTags[1].ti_Data = ((emulScreen->Width >> 1) - 128 + 7) & ~15;
    specWinTags[2].ti_Data = ((emulScreen->Height >> 1) - 96);

    if ((specWin = (struct Window *) OpenWindowTagList(NULL, specWinTags))
	== NULL) cleanExit("couldn't open window", RETURN_ERROR);

    SetMenuStrip(backWin, mainmenu);
    SetMenuStrip(specWin, mainmenu);
}



/*
	setupAudio: allocate audio channels and prepare for playing
	the sound.
*/
void setupAudio(void)
{
    /*	Select a stereo pair if possible, and a single channel otherwise
    */
    static UBYTE chans[] = { 3, 5, 10, 12, 1, 2, 4, 8 };

    if ((AIOptr1 = (struct IOAudio *) AllocRemember(&rememberKey,
	sizeof(struct IOAudio), MEMF_ANY | MEMF_PUBLIC | MEMF_CLEAR ))
	== NULL) cleanExit(memory_msg, RETURN_ERROR);

    if ((AIOptr2 = (struct IOAudio *) AllocRemember(&rememberKey,
	sizeof(struct IOAudio), MEMF_ANY | MEMF_PUBLIC | MEMF_CLEAR ))
	== NULL) cleanExit(memory_msg, RETURN_ERROR);

    if ((audioPort = CreateMsgPort()) == NULL)
	cleanExit("audio I/O port creation failed", RETURN_ERROR);

    audioSig = 1L << audioPort->mp_SigBit;


    /*	Basic set up for opening audio.device
    */
    AIOptr1->ioa_Request.io_Message.mn_ReplyPort = audioPort;
    AIOptr1->ioa_AllocKey = 0;

    /*	Set up for automatic channel allocation
	(We don't allow stealing our channels, for now.)
    */
    AIOptr1->ioa_Request.io_Message.mn_Node.ln_Pri = 127;
    AIOptr1->ioa_Request.io_Flags = ADIOF_NOWAIT;
    AIOptr1->ioa_Data = chans;
    AIOptr1->ioa_Length = sizeof(chans);

    audioOpen =
	OpenDevice(AUDIONAME, 0L, (struct IORequest *) AIOptr1, 0L);

    if (audioOpen) cleanExit("couldn't open audio device", RETURN_ERROR);


    /*	Set up request blocks for playing sound data
	(both channels play the same data)
    */
    AIOptr1->ioa_Request.io_Command = CMD_WRITE;
    AIOptr1->ioa_Request.io_Flags = ADIOF_PERVOL;
    AIOptr1->ioa_Data = soundMem;
    AIOptr1->ioa_Length = SOUNDMEM_SIZE;
    AIOptr1->ioa_Period = 124;	/* suitable for both PAL and NTSC */
    AIOptr1->ioa_Cycles = 0;	 /* forever */
    AIOptr1->ioa_Volume = 60;

    *AIOptr2 = *AIOptr1;    /* copy Audio IO Request structure */


    /*	Set chan1 = right channel and chan2 = left channel. One of these
	(but not both) can be zero. Otherwise, chan1 is 8 or 1, and chan2
	is 2 or 4.
    */
    chan1 = 9 & (ULONG) AIOptr1->ioa_Request.io_Unit;
    chan2 = 6 & (ULONG) AIOptr1->ioa_Request.io_Unit;

    /*	Set up the request blocks to use one channel each
    */
    AIOptr1->ioa_Request.io_Unit = (struct Unit *) ((ULONG) chan1);
    AIOptr1->ioa_Request.io_Unit = (struct Unit *) ((ULONG) chan2);

    /*	Start playing the allocated channels
    */
    if (chan1) {
	BeginIO((struct IORequest *)AIOptr1);
	sentIO1 = 1;
    }
    if (chan2) {
	BeginIO((struct IORequest *)AIOptr2);
	sentIO2 = 1;
    }
}



/*
	intSetup: Setting up and starting interrupt servers.

	Does not depend on any other part of the setup.
*/
void intSetup(void)
{
    if ((VBInterrupt = (struct Interrupt *) AllocRemember(&rememberKey,
	sizeof(struct Interrupt), MEMF_ANY | MEMF_PUBLIC | MEMF_CLEAR ))
	== NULL) cleanExit(memory_msg, RETURN_ERROR);

    VBInterrupt->is_Node.ln_Type = NT_INTERRUPT;
    VBInterrupt->is_Node.ln_Pri  = 0;
    VBInterrupt->is_Node.ln_Name = "Speccylator VB";
    VBInterrupt->is_Data = &intControl;
    VBInterrupt->is_Code = VBServer;

    intControl.Z80_Ctrl = Zctrl;

    AddIntServer(INTB_VERTB, VBInterrupt);
}



/*
	mainSetup: does most of the setup.

	Must be called by the control task.
*/
void mainSetup(void)
{
    /*	For most of the following function calls, the order
	is crucial, since they modify global variables.
    */

    {
	int i;

	/* get a signal for the Z80 task to reply through */
	if ((i = AllocSignal(-1)) == -1)
	    cleanExit(sigalloc_msg, RETURN_FAIL);
	ackSig = 1L << i;

	/* get a signal for use by the screen updater */
	if ((i = AllocSignal(-1)) == -1)
	    cleanExit(sigalloc_msg, RETURN_FAIL);
	updateSig = 1L << i;
    }

    setupScreen();

    backWinSig = 1L << backWin->UserPort->mp_SigBit;
    specWinSig = 1L << specWin->UserPort->mp_SigBit;

    getRasters();

    /* getBwCopList();	For quick-and-dirty copperlist screen only */

    {
	/* Initialise the screen updater
	*/
	int n = emulScreen->BitMap.BytesPerRow; /* an even number */
	softScr_SpBitmap = (ULONG)Zctrl->Zero + 0x4000;
	softScr_LineModulo = n;
	n = (specWin->LeftEdge >> 3) + (specWin->TopEdge * n);
	softScr_Bitplane0 = (ULONG)emulScreen->BitMap.Planes[0] + n;
	softScr_Bitplane1 = (ULONG)emulScreen->BitMap.Planes[1] + n;
	softScr_Bitplane2 = (ULONG)emulScreen->BitMap.Planes[2] + n;
	softScr_Bitplane3 = (ULONG)emulScreen->BitMap.Planes[3] + n;
	softScr_InitTable();
    }

    /*	Guarantee that an initial complete screen update
	is made (verifiesOn/Off sets UPDtype to 2).
    */
    intControl.UPDtype = 1;

    setupAudio();
}



/*
	openAmigaLibs: Opening system libraries.

	Should be called before doing anything else.
*/
void openAmigaLibs(void)
{
    /*	A bit of goto programming makes life easier */

    if ((IntuitionBase = (struct IntuitionBase *)
	OpenLibrary("intuition.library", 0)) == NULL) goto lib_err;

    if ((GfxBase = (struct GfxBase *)
	OpenLibrary("graphics.library", 0)) == NULL) goto lib_err;

    return;

lib_err:
    cleanExit("error opening library", RETURN_ERROR);
}



/*
	mainCleanup: does all the general cleanup.

	Called directly, or from a process-specific cleanup routine.
*/
void mainCleanup(void)
{
    /* (free any inactive UCopList first) */

    /*	Close windows and screen
    */
    {
	struct Message *p;
	if (backWin != NULL) {
	    ClearMenuStrip(backWin);
	    while (p = GetMsg(backWin->UserPort)) ReplyMsg(p);
	    CloseWindow(backWin);
	}
	if (specWin != NULL) {
	    ClearMenuStrip(specWin);
	    while (p = GetMsg(specWin->UserPort)) ReplyMsg(p);
	    CloseWindow(specWin);
	}
    }

    if (emulScreen != NULL) CloseScreen(emulScreen);

    if (!audioOpen) {
	if (sentIO1) AbortIO((struct IORequest *)AIOptr1);
	if (sentIO2) AbortIO((struct IORequest *)AIOptr2);
	CloseDevice((struct IORequest *) AIOptr1);
    }

    if (audioPort) {
	register struct Message *msg;
	while ((msg = GetMsg(audioPort)) != NULL) ;
	DeleteMsgPort(audioPort);
    }

    /*	Free memory
    */
    FreeRemember(&rememberKey, TRUE);
    if (loadBuf) FreeMem(loadBuf, loadBufSize);
}



/*
	Process-specific cleanup functions

*/

int basicCleanup(void) /* routine used before the task separation */
{
    /* standard cleanup */
    mainCleanup();

    /* terminate this process */

    return 1;	/* cleanup and exit from the loaded process */
}


int normalCleanup(void)
{
    /* process-specific part */

    if (VBInterrupt != NULL) RemIntServer(INTB_VERTB, VBInterrupt);

    if (z80TaskPtr != NULL) {
	/* then we can tell the emulator task to terminate */
	haltEmul();  /* wait for it to stop emulation */
	Signal(z80TaskPtr, SIGBREAKF_CTRL_C);
    } /* (otherwise it has already exited) */

    /* standard cleanup */
    mainCleanup();

    /* terminate this process */

    return 1;	/* cleanup and exit from the loaded process */
}  /* end of normalCleanup */


int ctrlTaskCleanup(void) /* for the debug mode input process */
{
    /* process-specific part */

    if (VBInterrupt != NULL) RemIntServer(INTB_VERTB, VBInterrupt);

    if (z80TaskPtr != NULL) {
	/* tell the loaded process to stop emulation and wait */
	haltEmul();  /* waits for acknowledge */
    } /* (otherwise it has already exited) */

    /* standard cleanup (can only be done when emulation has stopped) */
    mainCleanup();

    /* terminate this process */

    /* tell the loaded process to return from the emulation loop
       and proceed with its own cleanup and exit */
    Signal(z80TaskPtr, SIGBREAKF_CTRL_C);

    /* remove this process, returning stack memory and such */
    Exit(RETURN_OK); /* that's a dos.library Exit() call */

    return 0; /* a formality - block process on accidental return */
}  /* end of ctrlTaskCleanup */


int debugCleanup(void)	    /* for the debug mode main process */
{
    /* terminate this process */

    return 1;	/* cleanup and exit from the loaded process */
}



/*
	This is the code for the input control process
	used when run in debug mode.

*/
__geta4 /* So it can be started as a separate task (DICE C) */

void ctrlTask(void)
{
    cleanup_fp = ctrlTaskCleanup;

    mainSetup();

    intSetup();  /* start interrupt server */

    parseSignals();

    cleanExit(NULL, RETURN_OK);

}  /* end of ctrlTask */




/*
 *	main()
 *
 */

void main(int argc, char **argv)
{
    cleanup_fp = basicCleanup;

    openAmigaLibs();


    /*	Parse command line arguments
    */
    while (--argc > 0 && **++argv == '-') {
	if ((*argv)[1] == 'd') debug = 1;
	else cleanExit("unknown option", RETURN_ERROR);
    }

    if (argc > 0) snapshot = *argv;


    /*	Do some necessary initialisation stuff that does not
	depend on which process is doing it (but has to be
	done before starting the Z80 emulation)
    */
    allocMemory();
    loadROM();


    /*	Set up the initial Z80 control structure parameters...
    */
    Zctrl->Memory = z80Mem;
    Zctrl->CacheMem = cacheMem;
    Zctrl->AttrMem = attrMem;
    Zctrl->MemHandler = UsrMemHandler;

    /*	...and initialise the control structure.
    */
    if (Z80_Init(Zctrl)) cleanExit(
	"error initialising Z80 control structure", RETURN_ERROR);


    /*	Initialise Z80 memory and set address attributes
    */
    Z80_SetMemAttr(Zctrl, (UWORD) 0, (ULONG) 0x4000, Z80_MEM_ROM);
    /* (The memory is cleared at allocation, so the rest is RAM.) */


    /*	Initialise the Envdata structure fields
	(The soundMem pointer is set in the allocMemory() call above.)
    */
    {
	int i;
	UBYTE *p = Zctrl->Envdata.Kbd;
	for (i=0; i < 8; i++) *(p++) = 0xff;
    }
    Zctrl->Envdata.SndPtr = soundMem;


    /*	Load any specified snapshot file
    */
    if (snapshot) {
	if (!loadSnapshot(snapshot))
	    warmStart = -1;   /* set flag in asm.a */
    }


    /*	Now separate the two task spawning cases
    */
    if (debug) {

	/* The (Devpac) debugger can only trap the exceptions of the
	   main task it is running. Spawned tasks cause their own (not
	   trapped) exceptions. So, the loaded process will do the Z80
	   emulation, and we start another process to take over the
	   input handling.
	*/

	static struct TagItem p_tags[] = {
	    { NP_Entry, 0 },
	    { NP_Output, 0 }, { NP_Error, 0 },
		/* the entries above this line are set up below */
	    { NP_Priority, 0 }, { NP_Name, "Speccylator control" },
	    { NP_CloseOutput, FALSE }, { NP_CloseError, FALSE },
	    { TAG_DONE, 0 }
	};
	int v;

	/* The loaded process (this one) will be the Z80 task
	*/
	z80TaskPtr = (struct Task *) FindTask(NULL);

	p_tags[0].ti_Data = ctrlTask;	/* the code to execute */

	/* Use the output streams of the loaded process
	*/
	p_tags[1].ti_Data = ((struct Process *)z80TaskPtr)->pr_COS;
	p_tags[2].ti_Data = ((struct Process *)z80TaskPtr)->pr_CES;

	/* Start the control process. Nothing in this process may cause
	   a cleanExit() call until the control process has ended.
	*/
	if ((ctrlTaskPtr = (struct Task *)CreateNewProc(p_tags)) == NULL) {
	    cleanExit("couldn't create control process", RETURN_ERROR);
	}

	SetTaskPri(z80TaskPtr, -1); /* lower the priority of this task */

	/* Run emulation. The control process should terminate by itself
	   after signalling the Z80 emulation (this process) to exit.
	*/
	v = z80task();

	/* Set the cleanup pointer for use by this process
	*/
	cleanup_fp = debugCleanup;

	/* Now we can proceed safely
	*/
	if (v) cleanExit("an error occurred in the Z80 task", RETURN_ERROR);


    } else {

	/* This is the normal behaviour; the code is fairly obvious.
	*/

	mainSetup();

	ctrlTaskPtr = (struct Task *) FindTask(NULL);

	if ((z80TaskPtr = (struct Task *)
		CreateTask("Z80", -1, z80task, 1000)) == NULL)
	    cleanExit("couldn't create Z80 emulation task", RETURN_ERROR);

	cleanup_fp = normalCleanup;	 /* set cleanup pointer */

	intSetup();	/* set up and start interrupt servers */

	parseSignals();
    }

    cleanExit(NULL, RETURN_OK);

}   /* End of main() */


