/*
 DLM_Z0.C

 Down Load Manager - Z0 version.
 
 Important Notice: This program is provided as a sample only, with no guarantee
                   of robustness.

 This program is for downloading of programs to Z-World controller boards via
 RS232 serial port Z0. 
  
 This DLM works on a direct serial line or through a modem. The downloaded
 program (DLP) shares the z0 serial vector with the DLM and must reserve the z0
 port for DLM use.

 The DLM is best run in Flash EPROM, but may be run in RAM or standard EPROM.

 The DLP may be downloaded to RAM or Flash EPROM.

 Two break processing modes are implemented for executing this DLM. The first
 is the generation of a board reset with subsequent execution of the DLM, and
 the second is a direct jump to the download manager upon reception of a break
 signal. If the board reset option is chosen, the watchdog must be enabled. See
 the notes for the "handleBreak()" function below.

 The DLM can work while debugging in Dynamic C except that Dynamic C polling
 must be turned off before jumping to the DLP for the jump to succeed.

 The memory map parameters of the DLM ->*** AS IT WILL BE RUN TO DOWNLOAD ***<-
 must be provided to Dynamic C when compiling the DLP. The map is provided by
 the DLM itself.	

 WARNING: Use of the Terminal.exe program that comes with Windows is
          discouraged because Terminal.exe has proven unreliable in tests. 

 NOTE: If the downloaded program is to use the z0 channel, it must be re-opened
       with the function aascDLPReOpen() instead of aascOpen().

 Instructions for use:
 
 1. Set DLM_PASSWORD_STR to the desired password, or just "" for no password.

 2. Connect port z0 of the target to a dumb terminal.

 3. Compile this program to RAM or EPROM and run it. A menu should appear on
    the dumb terminal.

 4. Choose option 1 and enter the password on the dumb terminal. If no password
    is defined, just press ENTER when the password is requested.

 5. Choose option 3 "Report DLM Parameters" and write down the numbers exactly
    as reported, including internal colon separators (EG: "00:3fd8").

 6. In Dynamic C, open the program file you wish to create a DLP from.

 7. On the Options menu, select "Compiler...". Click on the ".DLP for download"
    radio button, then click the OK push button.

 8. Choose "Compile to File" on the Compile menu. The Download Manager
    Parameters dialog box will pop up.

 9. Enter the exact numbers from the dumb terminal display in the corresponding
    fields on the dialog box. Choose download to Flash or RAM if applicable.

 10. Click the OK button on the Download Manager Parameters dialog box to
     create a downloadable, compiled .DLP extension file named with the same
     name as your program file.

 11. If the dumb terminal and the PC running Dynamic C are not the same
     machine, copy the DLP file to a convenient directory on the dumb terminal.

 12. On the dumb terminal, choose option 1 and enter the password again. This
     step may be omitted if the DLM has not timed out (restarted) since the
     password was last entered.

 13. Choose option 4 to initiate the DLM's XMODEM download function. Then
     start the XMODEM upload of the DLP using the communications program
     (ProComm does this with the Page Up and X keys).

 14. After the transfer is complete, the DLP is ready to run. Choose option 5
     on the dumb terminal run the DLP.

 15. To terminate the DLP and return to the DLM, issue an RS-232 line break
     request from the dumb terminal (Alt-B in ProComm). The Down Load Manger
     menu should reappear on the dumb terminal after the target's watch dog
     times out. The target must have its jumpers set to run the program in RAM
     if you are running the DLM in RAM.

 Password Security:

 DLM_PASSWORD_LVL 0 : password initialized as DLM_PASSWORD_STR can be reset at
                      runtime		 .

 DLM_PASSWORD_LVL 1 : password given by DLM_PASSWORD_STR is unchangeable at
                      runtime.

 For no password, set DLM_PASSWORD_STR to "" and DLM_MIN_PW_LEN to 0.
*/

#ifdef AASC_DLM_DEVICE
#error "#define AASC_DLM_DEVICE only in Down Loaded Programs (DLPs)"
#fatal "when sharing an AASC device via the aascDLPReOpen function."
#endif

// save some root data memory by minimizing DLM's MAX_CHAN
#define MAX_CHAN 1	// change if more needed in DLM (no effect on DLP)

#define DLM_TIMEOUT 50000		// general time limit for serial funtions (ms)
#define PSW_TIMEOUT 10000		// time limit to enter password (ms)
#define HANGUP_TIMEOUT 7000   // time limit to hangup modem (ms)

#define DLM_PASSWORD_STR ""	// NULL string means no password, just hit enter
#define DLM_PASSWORD_LVL 0	  	// 0 = runtime changeable	
#define DLM_MAX_PW_LEN 0x10  	// max password length
#define DLM_MIN_PW_LEN 0x00  	// min password length

#define ERR_MESS_DELAY 2000  	// delay before blanking error messages (ms)

#define RECBUFFSIZE  2000		// XMODEM transfer buffer size	

#memmap xmem    					// comment out to RUN DLM in root memory
#nodebug								// comment out to debug DLM

#nointerleave
#use "vdriver.lib"
#use "circbuf.lib"
#use "aasc.lib"
#use "aascz0.lib"

#if (BOARD_TYPE == LP31xx)
#use eziolp31.lib
#endif

#if ROM==1
#INT_VEC SER0_VEC _z0Int;
#endif

//*** define and allocate storage for DLP segment data 
typedef struct{
	unsigned long len;
	unsigned long addr;
	unsigned CRC;
}CRCInfo;
typedef struct{
	char nseg;
	CRCInfo Seg[6];
}segInfo;
segInfo segments;

typedef struct{
	unsigned long address;
	unsigned long length;
	char reserved[8];
}segHeader;

extern char OPMODE;	// bit 7 set if controller has Flash EPROM.

unsigned long RAMSTART;

char password[DLM_MAX_PW_LEN];
char pword[DLM_MAX_PW_LEN];
char XMTransfrBuf[RECBUFFSIZE];
char DLM_parms_str[4][33];
char RdBuf[0x220];
char WrBuf[0x100];
char newCBAR,timeout;
int  clear_to_enter;
unsigned num_seg;
unsigned DLPCodeStart; 
unsigned long bounds[8];
unsigned long BBR_SHIFTED;

struct _Channel * chanNum;

char *Menu[] =
{
	"\x1b[2J",						// blank the screen
	"Download Manager Menu",
	"1) Enter Password",
	"2) Set Password",
	"3) Report DLM Parameters",
	"4) Download program",
	"5) Execute downloaded program",
	"6) Hang up remote modem",
	""
};

// allocate xmem (preferably Flash EPROM) memory bounds info storage
// Note: If not in Flash then no corrupted RAM protection for DLP!
xdata xmemBounds {
	0L,	// bounds[0], DLP root code bottom
	0L,	// bounds[1], DLP root code top
	0L,	// bounds[2], DLP xmem code bottom
	0L,	// bounds[3], DLP xmem code top
	0L,	// bounds[4], DLP root data bottom
	0L,	// bounds[5], DLP root data top
	0L,	// bounds[6], DLP xmem data bottom
	0L		// bounds[7], DLP xmem data top
};

// allocate xmem (preferably Flash EPROM) DLP segment data storage
// Note: If not in Flash then no corrupted RAM protection for DLP!
xdata xmemSegments {
	'\0',	// segments.nseg
	0L,	// segments.CRCInfo[0].len
	0L,	// segments.CRCInfo[0].addr
	0,		// segments.CRCInfo[0].CRC
	0L,	// segments.CRCInfo[1].len
	0L,	// segments.CRCInfo[1].addr
	0,		// segments.CRCInfo[1].CRC
	0L,	// segments.CRCInfo[2].len
	0L,	// segments.CRCInfo[2].addr
	0,		// segments.CRCInfo[2].CRC
	0L,	// segments.CRCInfo[3].len
	0L,	// segments.CRCInfo[3].addr
	0,		// segments.CRCInfo[3].CRC
	0L,	// segments.CRCInfo[4].len
	0L,	// segments.CRCInfo[4].addr
	0,		// segments.CRCInfo[4].CRC
	0L,	// segments.CRCInfo[5].len
	0L,	// segments.CRCInfo[5].addr
	0		// segments.CRCInfo[5].CRC
};

//*** Prototypes
int ProcessSegSec(unsigned long sectionSize, char currentSec, unsigned long Dest);
void hex_strcat(char * str,unsigned long N,int len);
firsttime unsigned timedAascReadChar( struct _Channel *chanNum,char *Dest);
firsttime unsigned timedAascWriteBlk( struct _Channel *chanNum, void *Src, unsigned len, char flags );
firsttime int timedAascGetStr( struct _Channel *chanNum, char *str, int len, int echo);
firsttime unsigned timedAascWrBfFree( struct _Channel *chanNum);
firsttime unsigned timedAascWrBfLeft( struct _Channel *chanNum);
void handleBreak();
unsigned long int a24to_a32(unsigned long int a24);
indirect void ReadCallBack(unsigned PackSize, char PackNum, unsigned status);
indirect unsigned RdCBackLocLg(unsigned PackSize, char PackNum);
int OK_to_jump(int keepCRC);
void PatchDLP(void);

// dummy out flash routine if using Smartblock
#if(BOARD_TYPE==SMARTBLOCK||BOARD_TYPE==LITTLE_GIANT||BOARD_TYPE==PLC_BOARD|| \
	 BOARD_TYPE==TINY_GIANT)
	int WriteFlash(unsigned long physical_addr, char *buf, int count){}
#endif

/**************************************************************************
MAIN
**************************************************************************/

main() {

	char menuResp[0x3];
	int i,first; 

	//*** set password to hardcoded one
	// Note: This is not the standard library sysIsSuperReset function, it is
	//       part of the DLM source.
	if(sysIsSuperReset()){
		strncpy(password,DLM_PASSWORD_STR,DLM_MAX_PW_LEN);
		segments.nseg = 0;
	}
	
	//*** need these for some address arithmetic
	BBR_SHIFTED=(unsigned long)inport(BBR)<<12;
	RAMSTART=(unsigned long)*((*(char**)3)+50)<<12UL;	// BIOS' ram_start code
	                                                 	// (either 40h or 80h)

	if(OPMODE & 0x80){	// has Flash EPROM
		if(xmemBounds < RAMSTART){
			// get stored DLP memory bounds info from xmem Flash
			xmem2root(xmemBounds, (void *) bounds, sizeof(bounds));
		}
		if(xmemSegments < RAMSTART){
			// get stored DLP segments data from xmem Flash
			xmem2root(xmemSegments, (void *) &segments, sizeof(segments));
		}
	}
	
	first = 0;				// if not set screen will be blanked
	clear_to_enter = 0;  // until password entered
	z0Error = timeout = 0;
	
	//*** start millisecond timer for costatements
	//*** and do GLOBAL_INIT and start watchdog hitting
	VdInit();		

	chanNum = aascOpen(
		DEV_Z0, 1,
		(ASCI_PARAM_1200*8) |    // 9600 baud rate
		ASCI_PARAM_8N1 |
		ASCI_PARAM_CRTS, handleBreak);

	aascSetReadBuf(chanNum,RdBuf,sizeof(RdBuf));
	aascSetWriteBuf(chanNum,WrBuf,sizeof(WrBuf));

#if (BOARD_TYPE == LP31xx)
	//	flip the RS232 driver and CTS switches
	outport(0x4084,1);
	outport(0x4083,1);
	outport(0x4082,1);
#endif

	DI();
	aascFlush(chanNum);
	EI();
	aascRxSwitch(chanNum,1);
	aascTxSwitch(chanNum,1);

	while(1){
		
		//*** wait PSW_TIMEOUT ms for remote user to enter password
		//*** then try to hang up modem and jump to DLP
		costate{
			waitfor(clear_to_enter || (!clear_to_enter && (timeout = DelayMs(PSW_TIMEOUT))));
			if(timeout)waitfor(HangUpModem());
		}

		costate{

			//*** display menu
			i = first;
			first = 1;
			while(strlen(Menu[i])){
				waitfor(Message(i+1,1,Menu[i],0));
				i++;
			}

			//*** get menu choice
			menuResp[0] = 0;
			while(menuResp[0] < 0x31 || menuResp[0] > 0x36){
				waitfor(Message(i+1,1,"Enter Choice:                       ",0));
				waitfor(SetCursor(i+1,17));
				waitfor(timedAascReadChar(chanNum,menuResp));
				waitfor(SetCursor(i+1,17));
			}

			//*** process choice
			switch(menuResp[0]){
				case 0x31 : 
					waitfor(enter_password());
					break;
				case 0x32 :
					if(clear_to_enter){
						waitfor(set_password());
						break;
					}
					else{
						goto access_denied;
					}
				case 0x33 :  
					if(clear_to_enter){
						waitfor(report_DLM_parms());
						break;
					}
					else{
						goto access_denied;
					}
				case 0x36 :  
						waitfor(HangUpModem());
						// if we got here jump to DLP failed
						waitfor(Message(9,1,"No Valid DLP Program Loaded!!    ",ERR_MESS_DELAY));
						break;
				case 0x34 :
					if(clear_to_enter){
						waitfor(i=download_program());
						if(i==XX_SUCCESS){
							waitfor(Message(9,17,"Checking CRCs                 ",0));
							if(OK_to_jump(1)){	// keep (don't update) segment CRCs
								waitfor(Message(9,17,"Good CRCs    ", ERR_MESS_DELAY));
								// DLP is good, go modify its vector table and
								// pass DLM's channel indirect function pointers
								PatchDLP();
								// fix the DLP's patched segments' CRCs
								OK_to_jump(0);	// update (don't keep) segment CRCs
							}
							else{
								waitfor(Message(9,17,"Bad CRCs     ", ERR_MESS_DELAY));
							}
						}
						else{
							waitfor(Message(9,1,"Download Unsuccessful!!          ",
								ERR_MESS_DELAY));
						}
						break;
					}
					else{
						goto access_denied; 
					}
				case 0x35 :
					waitfor(Jump_To_DLP(1));
					// if we got here jump to DLP failed
					waitfor(Message(9,1,"No Valid DLP Program Loaded!!    ",ERR_MESS_DELAY));
					break;	

				default : break;

access_denied:	// use goto to reduce code space		
					waitfor(Message(9,1,"Must Enter Valid Password First!!",ERR_MESS_DELAY));

			}
			waitfor(!timedAascWrBfLeft(chanNum));
		}	
	}
}

/**************************************************************************
void handleBreak();

	This function is called by the serial interrupt when a break request is
	detected (eg: Alt-B in Procomm). This function identifier is the last parameter
	in the call to aascOpen. Many applications will probably want to modify this
	function.

***************************************************************************/
root nodebug void handleBreak(){
// The tight loop with interrupts off causes a watchdog timeout which may be
// the preferable way of restarting the DLM depending on the application.

#if 1
	DI();
	for(;;);
#endif

// . . . OR . . .
// Disable the 2 lines of C code above (change '#if 1' to '#if 0') and
// enable the block of asm code between the #asm/#endasm directives
// below (change '#if 0' to '#if 1') to have this function reset the
// stack pointer and then jump directly to the DLM without a reset.

#if 0
#asm 
	di
				
	; use system stack
	ld hl,(3)		; BIOS' 3-table (set-up info) address
	ld de,29			; offset to debug stack address
	add hl,de		; ptr to 3-table's debug stack address
	ld a,(hl)		; LSB of debug stack address
	inc hl
	ld h,(hl)		; MSB of debug stack address
	ld l,a

	dec hl			; skip over BIOS' user return address
	dec hl			; (stacked before BIOS' jump to DLM)

	ld sp,hl			; debug (system) stack pointer

	ld a,0xe2		; set 8K Common Area 0 (root BIOS), 48K Bank Area
	out0 (CBAR),a	; (root code+data), and 8K Common Area 1 (xmem).

	jp 0x2200		; jump to the DLM's startup code (see "Hex File
						; Information" in the File Formats appendix of
						; the Dynamic C Technical Reference manual).
#endasm
#endif
}

/**************************************************************************
Jump_To_DLP(char flag)

	flag non-zero means OK to send messages. flag should be set to zero if serial
 	functions have timed out since it is safe to assume communication has failed.

 	Here is a summary of what this function does:	

	1. Write the previously saved vector table back to the DLPs vector table
	2. Do CRC check on entire DLP, return if bad CRC	
	3. Blank the remote terminal screen (if flag non-zero)
	4. Write the address of the z0 serial interrupt routine to the DLPs vector table
	5. Compute new CBAR for DLP
	6. Turn off interrupts
	7. Set the stack pointer to the system stack and jump to DLP
**************************************************************************/
root nodebug Jump_To_DLP(char flag){ 			// keep in root
	int retval;

	costate{
		retval = 0;

		if(flag)waitfor(Message(9,17,"Checking CRCs",0));
		if(OK_to_jump(1)){	// keep (don't update) segment CRCs

			//*** blank screen
			if(flag){
				waitfor(Message(1,1,Menu[0],0));
				waitfor(!timedAascWrBfLeft(chanNum)&&DelayMs(60L));
			}

			//*** set DLPCodeStart to vector table
			if((OPMODE & 0x80) && (bounds[0]<RAMSTART)){			//	if flash
				DLPCodeStart = (unsigned)(bounds[0]);
			}
			else{
				DLPCodeStart = (unsigned)(bounds[0]-BBR_SHIFTED);
			}	

			//*** set to start up code
			DLPCodeStart += 0x0200;

			//*** new CBAR
			if((!(OPMODE & 0x80) && (ROM==0))){
				newCBAR = inport(CBAR);
			}
			else{
				if(bounds[0]<RAMSTART){
					newCBAR = inport(CBAR);
				}
				else newCBAR = ((DLPCodeStart >> 12)&0x0f)+0xe0;
			}

			//*** turn off clock interrupts
			IRES(TCR, 1);
			IRES(TCR, 5);
			IRES(TCR, 7);
#asm 
			di
					
			;*** use system stack
			ld hl,(3)		; BIOS' 3-table (set-up info) address
			ld de,29			; offset to debug stack pointer
			add hl,de		; ptr to 3-table's debug stack address
			ld a,(hl)		; LSB of debug stack address
			inc hl
			ld h,(hl)		; MSB of debug stack address
			ld l,a

			dec hl			; skip over BIOS' user return address
			dec hl			; (stacked before BIOS' jump to DLM)

			ld sp,hl			; debug (system) stack address

			ld hl,newCBAR	; set Common Area 0 (root BIOS),
			ld a,(hl)		; Bank Area (root code+data), and
			out0 (CBAR),a	; Common Area 1 (xmem) to suit DLP.

			ld hl,(DLPCodeStart)
			jp (hl)
#endasm
		}
		retval = 1;
	}
	return retval;
}

/**************************************************************************
	int enter_password();
**************************************************************************/
nodebug int enter_password(){
	int retval;
	costate{
		retval = 0;
		waitfor(Message(9,1,"Enter password:        ",0));
		waitfor(SetCursor(9,18));
		waitfor(timedAascGetStr(chanNum, pword, strlen(password), -1));
		if(!strcmp(password,pword)){  // compare with stored password
			clear_to_enter = 1;
			waitfor(Message(9,1,"",1));
		}
		else{
			waitfor(Message(9,1,"Invalid Password!!                  ",ERR_MESS_DELAY));
		}
		retval = 1;
	}
	return retval;
}

/**************************************************************************
	int set_password();
**************************************************************************/
nodebug int set_password(){
	int retval;
	char tpswd[DLM_MAX_PW_LEN];
	costate{
		retval = 0;
		if(DLM_PASSWORD_LVL){
			waitfor(Message(9,1,"Password Changing Not Enabled",ERR_MESS_DELAY));
		}
		else{
			waitfor(Message(9,1,"Enter New Password: ",0));
			waitfor(SetCursor(9,22));
			waitfor(aascGetsWF(chanNum, tpswd, sizeof(tpswd), -1));
			waitfor(Message(9,1,"Enter Again to Verify:               ",0));
			waitfor(SetCursor(9,24));
			waitfor(aascGetsWF(chanNum, pword, sizeof(tpswd), -1));
			if(!strcmp(tpswd,pword) && strlen(tpswd)>=DLM_MIN_PW_LEN){
				strncpy(password,pword,DLM_MAX_PW_LEN);
			}
			else{
				waitfor(Message(9,1,"Invalid Password!!                ",ERR_MESS_DELAY));
				waitfor(SetCursor(8,1));
			}
		}
		retval = 1;
	}
	return retval;
}

/**************************************************************************
	int report_DLM_parms();
**************************************************************************/
nodebug int report_DLM_parms(){
	int retval,i;
	costate{
		retval = 0;

		// prog_param structure is always available and filled in
		// a Dynamic C program
		strcpy(DLM_parms_str[0],"DLM Root Code Top :      ");
		hex_strcat(DLM_parms_str[0],prog_param.RCE.aaa.a.base,2);
		strcat(DLM_parms_str[0],":"); 
		hex_strcat(DLM_parms_str[0],prog_param.RCE.aaa.a.addr,4);

		strcpy(DLM_parms_str[1],"DLM Root Data Bottom :   ");
		hex_strcat(DLM_parms_str[1],inport(BBR),2);
		strcat(DLM_parms_str[1],":"); 
		hex_strcat(DLM_parms_str[1],prog_param.RDB.aaa.a.addr,4);

		strcpy(DLM_parms_str[2],"DLM Xmem Code Top    :  ");
		hex_strcat(DLM_parms_str[2],a24to_a32(prog_param.XCE.l),8);

		strcpy(DLM_parms_str[3],"DLM Xmem Data Bottom :  ");
		hex_strcat(DLM_parms_str[3],a24to_a32(prog_param.XDB.l),8);

		for(i=0;i<4;i++)waitfor(Message(11+i,1,DLM_parms_str[i],0));
		retval = 1;
	}
	return retval;
}

/**************************************************************************
	int SetCursor(int row, int col);
**************************************************************************/
nodebug int SetCursor(int row, int col){
	// ASCII terminal set cursor command is esc[row;colH
	int retval;
	char buf[8];
	retval=0;
	costate{
		waitfor(timedAascWrBfFree(chanNum)>=8);
		buf[0] = '\x1b';
		buf[1] = '[';
		buf[2] = row/10 + 0x30;
		buf[3] = row - 10*(row/10) + 0x30;
		buf[4] = ';';
		buf[5] = col/10 + 0x30;
		buf[6] = col - 10*(col/10) + 0x30;
		buf[7] = 'H';
		waitfor(retval=(timedAascWriteBlk(chanNum,buf,8,0)==8));
	}
	return retval;
}

/**************************************************************************
	unsigned long(unsigned long int a24);
	convert logical to physical address
**************************************************************************/
nodebug unsigned long int a24to_a32(unsigned long int a24)
{
	auto unsigned int tmp;

	*( (char *)&a24 + 3) = 0; // now it is really a three byte
	tmp = (unsigned int)*( (char *)&a24 + 2) +
	((unsigned int)( *((char *)&a24 + 1) >> 4) & 0x0f);
	
	tmp <<= 4;
	a24 &= 0xfff;
	*(int *)((char *) &a24 + 1) |= tmp;
	return a24 & 0xfffff;
}

#define NUMSEG_OFFSET 32	  	// DLP file offset	
#define CHECKSUM_OFFSET 126  	// DLP file offset	


typedef struct {
	segHeader * pHeader;	   // points to current segment
	char currentSection;    // 0 = main header, 1 = seg header, 2 = seg body, 3 = seg CRC
	unsigned long curSecOffset;
}segReadState;

/**************************************************************************
	int download_program();
**************************************************************************/
segReadState segState;
int iseg,i;
unsigned CRCt ;

nodebug int download_program(){
	int retval;
	unsigned long address;
	costate{
		retval = 0;
		num_seg  = 1;
		memcpy(&segState,"\x0",sizeof(segState));
		segState.currentSection = 0;
		iseg = 0;

		waitfor(Message(9,1,"Initiate XMODEM transfer on PC",0));

		waitfor(DelayMs(1500L));
		aascRxSwitch(chanNum,1);
		aascTxSwitch(chanNum,1);
		DI();
		aascFlush(chanNum);
		EI();

		// do the transfer
		aascXMRdInitLog((unsigned)XMTransfrBuf,128);
		aascReadXModem(chanNum, RdCBackLocLg, ReadCallBack, 1);
		waitfor(retval=aascReadXModem(chanNum, RdCBackLocLg, ReadCallBack, 0));
		segments.nseg = num_seg;
	}
	return retval;
}

/**************************************************************************
unsigned RdCBackLocLg(unsigned PackSize, char PackNum);

	(parameters not used here) callback function used by aascReadXModem.
	returns the start address where the next XModem packet should be read into.
	Here we keep using the same address. Since the callback confirm function does
	the transfer to the final destination 
**************************************************************************/
indirect nodebug unsigned RdCBackLocLg(unsigned PackSize, char PackNum){
	return (unsigned)XMTransfrBuf;
}

/**************************************************************************
	void ReadCallBack(unsigned PackSize, char PackNum, unsigned status);

	callback confirm function called by aascReadXModem() after a packet 
	(128 or 1024) bytes is received. The identifier ReadCallBack is passed
	as a parameter to aascReadXModem().

	This function interprets and processes each packet as a part of a segmented
	DLP file. A state machine type algorithm is used here.

	See "The DLP File Format" in the Remote Download chapter of the Dynamic C
	Technical Reference manual for information on the packets being processed.
**************************************************************************/
segHeader Sheader;
unsigned long bytesRemaining;
unsigned long curPakOffset;

indirect nodebug void 
ReadCallBack(unsigned PackSize, char PackNum, unsigned status){
	int i;
	segState.pHeader = &Sheader;

	bytesRemaining=PackSize;
	curPakOffset = 0;
	while(bytesRemaining > 0 && iseg < num_seg){
		switch(segState.currentSection){
			case 0 : // processing main 128-byte header of DLP file

				//*** copy the 8 memory boundaries 
				for(i=0;i<32;i=i+4){
					memcpy((char*)((unsigned long)bounds+i),
							  (char*)((unsigned long)XMTransfrBuf+i), 4);
				}

				//*** get number of segments and CRC for this header
				memcpy((char*)((unsigned long)&num_seg),
						 (char*)((unsigned long)XMTransfrBuf+NUMSEG_OFFSET), 2);
				memcpy((char*)((unsigned long)&CRCt),
						 (char*)((unsigned long)XMTransfrBuf+CHECKSUM_OFFSET), 2);
				
				curPakOffset = 128;		// advance packet pointer
				bytesRemaining -= 128;
				segState.curSecOffset = 0;
				segState.currentSection = 1;  
				break;

			case 1 : // processing a segment header
				if(ProcessSegSec(sizeof(segHeader), segState.currentSection,
						(unsigned long)segState.pHeader+segState.curSecOffset)){
					segState.currentSection =  2;  
				}
				break;

			case 2 : // processing a segment body
				if(ProcessSegSec(segState.pHeader->length, segState.currentSection,
						(unsigned long)segState.pHeader->address+segState.curSecOffset))
					segState.currentSection =  3;  
				break;

			case 3 : //  processing a segment CRC
				if(ProcessSegSec(2L, segState.currentSection,
						(unsigned long)&CRCt+segState.curSecOffset)){

					//*** must save seg info for later CRC check
					segments.Seg[iseg-1].CRC = CRCt;
					segments.Seg[iseg-1].len = Sheader.length;
					segments.Seg[iseg-1].addr = Sheader.address;
					segState.currentSection =  1;  
				}
				break;

		}			  	// end switch
	}				// end while
}

/**************************************************************************
	int ProcessSegSec(unsigned long sectionSize, char currentSec, unsigned long Dest);

	sectionSize - size of the current section
	currentSec  - type of the current section
						1 = header, 2 = body, 3 = CRC
	Dest        - physical address of memory destination of body

	to be called until it returns non-zero when the section has been
	fully processed, returns zero when not done
**************************************************************************/
nodebug int ProcessSegSec(unsigned long sectionSize, char currentSec, unsigned long Dest){
	unsigned long size;

	if(bytesRemaining >= sectionSize-segState.curSecOffset){

		//*** all of remaining section is in this packet
		size = sectionSize-segState.curSecOffset; 

		//*** ready to process next section
		segState.curSecOffset = 0;
	}
	else{

		//*** some of section is in next packet(s)
		size = bytesRemaining; 
		segState.curSecOffset += size;
	}

	//*** copy received bytes to correct location
	if(currentSec!=2){     // Dest is in root, and this isn't a segment
		memcpy((char*)Dest,
			  (char*)((unsigned long)XMTransfrBuf+curPakOffset), (size_t) size);
	}
	else{			// transfer segment body
		if(OK_to_Write(Dest, Dest+size)){		
			if(Dest < RAMSTART && (OPMODE & 0x80))
				WriteFlash(Dest, 
					(char*)((unsigned long)XMTransfrBuf+curPakOffset) , (size_t) size);
			else 	// write to RAM
				root2xmem((char*)((unsigned long)XMTransfrBuf+curPakOffset),Dest,(size_t) size);
		}
	}

	if(segState.currentSection==3 && segState.curSecOffset == 0)
		iseg++;						// increment segment index
	
	curPakOffset += size;		// advance packet pointer
	bytesRemaining -= size;
	return !segState.curSecOffset; 
}

/**************************************************************************
void hex_strcat(char * str,unsigned long N,int len);

	convert N to an ASCII hex representation of length len
	and append it to str. len should be 2,4,6, or 8. str should already
	be NULL terminated, and this function moves the NULL terminator 
	after appending the hex number. Saves many K bytes from using sprintf.
**************************************************************************/
nodebug void hex_strcat(char * str,unsigned long N,int len){
	char *p,c,Ch;
	int slen;
	p = (char*)&N;
	slen = strlen(str);

	for(i=0;i<len;i+=2){			// bytes are stored least significant first
		Ch = *p++;					
		c = Ch & 0x0f;				// low nibble of byte 
		str[slen+len-i-1] = c > 0x9 ? c+55 : c+48; 
		c = (Ch & 0xf0) >> 4;	// high nibble of byte	
		str[slen+len-i-2] = c > 0x9 ? c+55 : c+48;
	}
	str[slen+i] = 0;  			// null terminate
}

/**************************************************************************
int Message(char row,char col,char * s, unsigned blank);

	put  str at (row,col) and blank out after blank	
	milliseconds if blank non-zero
**************************************************************************/
nodebug int Message(char row,char col,char * s, unsigned blank){
	int retval;
	costate{
		retval = 0;
		waitfor(SetCursor(row,col));
		waitfor(timedAascWrBfFree(chanNum)>=strlen(s));
		waitfor(timedAascWriteBlk(chanNum,s,strlen(s),0)==strlen(s));
		if(blank){
			waitfor(DelayMs(blank));
			waitfor(SetCursor(row,col));
			waitfor(timedAascWrBfFree(chanNum)>=33);
			waitfor(timedAascWriteBlk(chanNum,
					"                                 ",33,0)==33);
		}
		retval = 1;
	}
	return retval;
}

/**************************************************************************
int OK_to_jump(int keepCRC);

Performs CRC check on DLP program using data stored in segments structure

If keepCRC is non-0 then returns 0 on first bad segment CRC;
if keepCRC is 0 then updates segment CRC (fix for DLP patches) and then if
possible copies memory bounds array and segments structure to Flash.
**************************************************************************/
nodebug int OK_to_jump(int keepCRC){
	int i;
	unsigned long len, offset;
	unsigned crc;
	char buf[128];
	// check each CRC except first which is the NMI vector segment
	for(i = 1; i < segments.nseg ; i++){  
		crc = 0;
		offset = 0;
		while(offset < segments.Seg[i].len){
			len = segments.Seg[i].len-offset>128 ?
				   128 : segments.Seg[i].len-offset;
			xmem2root(segments.Seg[i].addr+offset,buf,(size_t) len);			
			offset += len;
			crc = getcrc(buf, (char) len, crc);
		}
		if(crc!=segments.Seg[i].CRC){
			if(keepCRC){
				return 0;
			}
			else{
				segments.Seg[i].CRC = crc;
			}
		}
	}
	if((!keepCRC) && (OPMODE & 0x80)){	// update Flash EPROM storage
		if(xmemBounds < RAMSTART){
			// save DLP's updated memory bounds information to xmem Flash
			WriteFlash(xmemBounds, (char *) bounds, sizeof(bounds));
		}
		if(xmemSegments < RAMSTART){
			// save DLP's updated segments information to xmem Flash
			WriteFlash(xmemSegments, (char *) &segments, sizeof(segments));
		}
	}
	return (i>1);
}

/**************************************************************************
int HangUpModem();

Attempts to issue modem hangup and reset commands and jumps to  DLP whether
it succeeds or not.
**************************************************************************/
nodebug int HangUpModem(){
	char c;

	//*** Give it HANGUP_TIMEOUT milliseconds to send modem commands and 
	//*** receive acknowledgement then assume communication has failed
	//*** and jump to DLP.	
	costate{
		waitfor(DelayMs(HANGUP_TIMEOUT));
		goto _Jump;
	}

	//*** don't use timed aasc functions since they call this function
	//*** above costatement task watches for timeout
	costate{
		waitfor(DelayMs(1500));
		waitfor(aascWriteBufFree(chanNum)>=3);
		waitfor(aascWriteBlk(chanNum,"+++",3,0)==3);
		waitfor(DelayMs(1500));
		waitfor(aascWriteBufFree(chanNum)>=5);
		waitfor(aascWriteBlk(chanNum,"ATH0\r",5,0)==5);
		waitfor(aascReadChar(chanNum,&c));	// wait for "OK" response
		waitfor(aascReadChar(chanNum,&c));	
		waitfor(aascWriteBufFree(chanNum)>=5);
		waitfor(aascWriteBlk(chanNum,"ATZ0\r",5,0)==5);
		waitfor(aascReadChar(chanNum,&c));
		waitfor(aascReadChar(chanNum,&c));  // wait for "OK" response
		goto _Jump;
	}
	return 0;

_Jump:
	while(!Jump_To_DLP(0));
	DI();
	while(1);
}

/**************************************************************************
int sysIsSuperReset()
 detects a super reset if no super reset returns zero, else 1

 Note: This function will also detect and refresh a corrupted RAM copy of
       the prog_param structure.
**************************************************************************/
nodebug int sysIsSuperReset() {
	static struct progStruct RamProgParam;

	if (!memcmp(&RamProgParam,&prog_param,sizeof(struct progStruct))) {
		_prot_recover();
		return 0;
	}
   else {
		memcpy(&RamProgParam,&prog_param,sizeof(struct progStruct));
		_prot_init();				//	protected variable not recovered here
		return 1;
	}
}

/**************************************************************************
timedAascXXXXXXX functions

these functions are calls to aascXXXXXX functions with timeout.
If any of these functions, while being called repeatedly in a waitfor or loop
fails to complete before DLM_TIMEOUT milliseconds, then HangUpModem is called.
The timing starts on the first call.
**************************************************************************/
nodebug firsttime unsigned timedAascReadChar(
	CoData * pfb, struct _Channel *chanNum,char *Dest){
	if(timed_out(pfb)) while(!HangUpModem());
	return aascReadChar(chanNum,Dest);
}
nodebug firsttime unsigned timedAascWriteBlk(
	CoData * pfb, struct _Channel *chanNum, void *Src, unsigned len, char flags ){
	if(timed_out(pfb)) while(!HangUpModem());
	return aascWriteBlk(chanNum,Src,len,flags);
}
nodebug firsttime int timedAascGetStr(
	CoData * pfb, struct _Channel *chanNum, char *str, int len, int echo){
	if(timed_out(pfb)) while(!HangUpModem());
	return aascGetsWF(chanNum, str, len, echo);
}
nodebug firsttime unsigned timedAascWrBfFree(
	CoData * pfb, struct _Channel *chanNum){
	if(timed_out(pfb)) while(!HangUpModem());
	return aascWriteBufFree(chanNum);
}
nodebug firsttime unsigned timedAascWrBfLeft(
	CoData * pfb, struct _Channel *chanNum){
	if(timed_out(pfb)) while(!HangUpModem());
	return aascWriteBufLeft(chanNum);
}
nodebug timed_out(CoData * pfb){
	if (pfb->firsttime)
	{
		pfb->content.ul = MS_TIMER;
		pfb->firsttime = 0;
	}
	return((MS_TIMER - pfb->content.ul) >= DLM_TIMEOUT);
}

/**************************************************************************
OK_to_Write

	check that memory location between start and end to make sure it doesn't
	overlap any DLM code or data in root or Xmem
**************************************************************************/
nodebug int OK_to_Write(unsigned long start, unsigned long end){
	return	(

		//*** check for overlap in root code and data
		!overlap(
			start, end, a24to_a32(0x2000),
					a24to_a32(prog_param.RCE.l))&&
		!overlap(
			start, end, a24to_a32(prog_param.RDB.l)+BBR_SHIFTED,
					a24to_a32(prog_param.RDE.l)+BBR_SHIFTED)&&

		//*** check for overlap in XMEM code 
		(!overlap(
			start, end, a24to_a32(prog_param.XCB.l),a24to_a32(prog_param.XCE.l)) 

			//*** OK if no XMEM code in DLM			
			|| (a24to_a32(prog_param.XCB.l)==a24to_a32(prog_param.XCE.l)) )&&

		//*** check for overlap in XMEM data
		(!overlap(
			start, end, a24to_a32(prog_param.XDB.l),a24to_a32(prog_param.XDE.l)) 

			//*** OK if no XMEM data in DLM			
			|| (a24to_a32(prog_param.XDB.l)==a24to_a32(prog_param.XDE.l)) )

				);
}
nodebug int overlap(unsigned long s1, unsigned long e1,	// return true if segment bounded by s1,e1
		  unsigned long s2, unsigned long e2){ // overlaps segment bounded by s2,e2

	return ( ((s2 >= s1 ) && ( s2 <= e1)) || 
				((e2 >= s1 ) && ( e2 <= e1)) || 
				((s1 >= s2 ) && ( s1 <= e2)) || 
				((e1 >= s2 ) && ( e1 <= e2))  	);
}

nodebug void PatchDLP(void)
{
	// get Z0 interrupt service address into write buffer
	*(unsigned *) WrBuf = (unsigned) _z0Int;
	
	// Patch the DLP's vector table and pass the DLM's AASC channel's
	// indirect function pointers (DevFunc) to the DLP just below its vector
	// table. The DLM's DevFunc pointers are installed by the DLP if the
	// aascDLPReOpen function is used, in the _aascInitDP function chain which
	// is called by _GLOBAL_INIT.
	// Note: See AASC.LIB's aascGetDLMChan function and references, also
	//       see DLM's xdata xmemBounds definition for memory bounds info.
	if((OPMODE & 0x80) && (bounds[0] < RAMSTART)){	// xmem Flash
		// store DLM's Z0 serial interrupt to DLP's vector table
		// to keep DLM's line break handler active
		WriteFlash(bounds[0] + SER0_VEC, WrBuf, 2);

		// pass DLM's DevFunc pointers to DLP
		WriteFlash(bounds[0] - 14, (char *) (chanNum->pDevFunc), 12);
	}
	else{	// xmem RAM
		// store DLM serial interrupt to DLP's vector table
		// to keep DLM's line break handler active
		root2xmem(WrBuf, bounds[0] + SER0_VEC, 2);

		// pass DLM's DevFunc pointers to DLP
		root2xmem((void*) (chanNum->pDevFunc), bounds[0] - 14, 12);
	}
}
