/*
 *	clipihex.c
 *
 *	インテルHEX形式ファイルローダー
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2015 Naoyuki Sawa
 *
 *	* Fri Nov 30 18:52:54 JST 2012 Naoyuki Sawa
 *	- 1st リリース。
 *	* Sun Jul 12 17:07:37 JST 2015 Naoyuki Sawa
 *	- IntelHex_load_r()を追加しました。
 */
#include "clip.h"

/* 参照資料:「Hexadecimal Object File Format Specification」(Revision A January 6, 1988) */

typedef struct _IntelHex_RECORD {
	unsigned char  RECTYP;
	unsigned char  RECLEN;
	unsigned short LOAD_OFFSET;
	unsigned char  INFO_or_DATA[255];
} IntelHex_RECORD;

static void IntelHex_load_RECORD(FILE* fp, IntelHex_RECORD* pRECORD);
static int IntelHex_load_1_byte(FILE* fp, unsigned char* p_sum);

void
IntelHex_load(const char* fname, void (*callback)(int addr, int data))
{
	//IntelHex_load()とIntelHex_load_r()は、callback関数の形式が異なりますが、
	//'cdecl呼出規約'ならば、IntelHex_load()形式のcallback関数において、余分な引数が無視されるので問題ありません。
	IntelHex_load_r(fname, (void (*)(int,int,void*))callback, NULL/*ダミー*/);
}

void
IntelHex_load_r(const char* fname, void (*callback)(int addr, int data, void* userdata), void* userdata)
{
	FILE* fp;
	int i, base;
	IntelHex_RECORD RECORD;

	/* ファイルを開く */
	fp = fopen(fname, "r");
	if(!fp) { DIE(); }
	/* レコードを処理する */
	base = 0;
	for(;;) {
		IntelHex_load_RECORD(fp, &RECORD);
		switch(RECORD.RECTYP) {
		case 0x00: /* Data Record */
			for(i = 0; i < RECORD.RECLEN; i++) {
				(*callback)(base + RECORD.LOAD_OFFSET + i, RECORD.INFO_or_DATA[i], userdata);
			}
			break;
		case 0x01: /* End of File Record */
			if((RECORD.RECLEN != 0) || (RECORD.LOAD_OFFSET != 0)) { DIE(); }
			goto L_EXIT; /* ここまで */
			break;
		case 0x02: /* Extended Segment Address Record */
			if((RECORD.RECLEN != 2) || (RECORD.LOAD_OFFSET != 0)) { DIE(); }
			base = ((RECORD.INFO_or_DATA[0] << 8) | RECORD.INFO_or_DATA[1]) << 4;
			break;
		case 0x03: /* Start Segment Address Record */
			if((RECORD.RECLEN != 4) || (RECORD.LOAD_OFFSET != 0)) { DIE(); }
			/* 無視する */
			break;
		case 0x04: /* Extended Linear Address Record */
			if((RECORD.RECLEN != 2) || (RECORD.LOAD_OFFSET != 0)) { DIE(); }
			base = ((RECORD.INFO_or_DATA[0] << 8) | RECORD.INFO_or_DATA[1]) << 16;
			break;
		case 0x05: /* Start Linear Address Record */
			if((RECORD.RECLEN != 4) || (RECORD.LOAD_OFFSET != 0)) { DIE(); }
			/* 無視する */
			break;
		default:
			DIE();
		}
	}
L_EXIT:	/* ファイルを閉じる */
	fclose(fp);
}

static void
IntelHex_load_RECORD(FILE* fp, IntelHex_RECORD* pRECORD)
{
	unsigned char sum;
	int i, c;
	/* RECORD_MARKを確認する */
	if(fgetc(fp) != ':') { DIE(); } /* RECORD_MARK */
	/* RECLEN〜CHKSUMを取得する */
	sum = 0;
	pRECORD->RECLEN       = IntelHex_load_1_byte(fp, &sum);
	pRECORD->LOAD_OFFSET  = IntelHex_load_1_byte(fp, &sum) << 8;
	pRECORD->LOAD_OFFSET |= IntelHex_load_1_byte(fp, &sum);
	pRECORD->RECTYP       = IntelHex_load_1_byte(fp, &sum);
	for(i = 0; i < pRECORD->RECLEN; i++) {
		pRECORD->INFO_or_DATA[i] = IntelHex_load_1_byte(fp, &sum);
	}
	IntelHex_load_1_byte(fp, &sum); /* CHKSUM */
	if(sum != 0) { DIE(); }
	/* 改行を確認する */
	              c = fgetc(fp);
	if(c == '\r') c = fgetc(fp);
	if(c != '\n') { DIE(); }
}

static int
IntelHex_load_1_byte(FILE* fp, unsigned char* p_sum)
{
	int hi, lo, c;
	/* 上位ニブルを取得する */
	hi = fgetc(fp);
	     if((hi >= '0') && (hi <= '9')) { hi -=  '0';       }
	else if((hi >= 'A') && (hi <= 'F')) { hi -= ('A' - 10); }
	else                                { DIE();            }
	/* 下位ニブルを取得する */
	lo = fgetc(fp);
	     if((lo >= '0') && (lo <= '9')) { lo -=  '0';       }
	else if((lo >= 'A') && (lo <= 'F')) { lo -= ('A' - 10); }
	else                                { DIE();            }
	/* 上位ニブルと下位ニブルを合成し、バイト値とする */
	c = (hi << 4) | lo;
	/* チェックサムに、バイト値を加算する */
	*p_sum += c;
	/* バイト値を返す */
	return c;
}

