/*
 *	clipsrec.c
 *
 *	モトローラS (SREC) 形式ファイルローダー
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2016 Naoyuki Sawa
 *
 *	* Tue Nov 10 23:10:00 JST 2015 Naoyuki Sawa
 *	- 1st リリース。
 *	  clipihex.cモジュールの関数とインターフェイス互換となるように作成しました。
 *	- 参照資料:「Wikipedia - SREC (file format)」http://en.wikipedia.org/wiki/SREC_(file_format)
 *	* Tue Sep 06 21:06:46 JST 2016 Naoyuki Sawa
 *	- 64ビット対応のために、getline()のサイズを保持する変数を(int)⇒(size_t)に修正しました。
 *	  64ビット対応のための準備ですが、32ビットでも(size_t)の方が本当は正しいからです。
 *	  とは言え、32ビットではどちらでも同じ結果になるので、既存の32ビットコードには影響有りません。
 */
#include "clip.h"
/****************************************************************************
 *	ローカル関数宣言
 ****************************************************************************/
static int SREC_get_byte(const char** pp, uint8_t* pByteCount, uint8_t* pChecksum);
/****************************************************************************
 *	グローバル関数
 ****************************************************************************/
void SREC_load(const char* fname, void (*callback)(int addr, int data)) {
	//SREC_load()とSREC_load_r()は、callback関数の形式が異なりますが、
	//'cdecl呼出規約'ならば、SREC_load()形式のcallback関数において、余分な引数が無視されるので問題ありません。
	SREC_load_r(fname, (void (*)(int,int,void*))callback, NULL/*ダミー*/);
}
/*--------------------------------------------------------------------------*/
void SREC_load_r(const char* fname, void (*callback)(int addr, int data, void* userdata), void* userdata) {
	char* glbuf = NULL;	//┬getline()用バッファ
//{{2016/09/06変更:64ビット対応のために、getline()のサイズを保持する変数を(int)⇒(size_t)に修正しました。
//	int   glcap = 0;	//┘
//↓2016/09/06変更:64ビット対応のために、getline()のサイズを保持する変数を(int)⇒(size_t)に修正しました。
	size_t glcap = 0;	//┘
//}}2016/09/06変更:64ビット対応のために、getline()のサイズを保持する変数を(int)⇒(size_t)に修正しました。
	FILE* fp = fopen(fname, "r");
	if(!fp) { DIE(); }	//ファイルが開けない
	while(getline(&glbuf, &glcap, fp) != EOF) {
		const char* p = glbuf;
		int RecordType;
		uint8_t ByteCount = 1, Checksum = 1;
		if(*p++ != 'S') { DIE(); }	//RecordTypeが不正
		RecordType = *p++ - '0';
		ByteCount = SREC_get_byte(&p, &ByteCount, &Checksum);
		switch(RecordType) {
		default:DIE();	//RecordTypeが不正
		case 1:
			//This record contains data that starts at the 16-bit address field.
			//This record is typically used for 8-bit microcontrollers, such as AVR, PIC, 8051, 68xx, 6502, 80xx, Z80.
			//The number of bytes of data contained in this record is "Byte Count Field" minus 3, which is 2 bytes for "16-bit Address Field" and 1 byte for "Checksum Field".
			/* FALLTHRU */
		case 2:
			//This record contains data that starts at a 24-bit address.
			//The number of bytes of data contained in this record is "Byte Count Field" minus 4, which is 3 bytes for "24-bit Address Field" and 1 byte for "Checksum Field".
			/* FALLTHRU */
		case 3:
			//This record contains data that starts at a 32-bit address.
			//This record is typically used for 32-bit microcontrollers, such as ARM and 680x0.
			//The number of bytes of data contained in this record is "Byte Count Field" minus 5, which is 4 bytes for "32-bit Address Field" and 1 byte for "Checksum Field".
			{
				int Address = 0;
				while(RecordType >= 0) {
					Address = (Address << 8) | SREC_get_byte(&p, &ByteCount, &Checksum);
					RecordType--;
				}
				while(ByteCount > 1) {
					int Data = SREC_get_byte(&p, &ByteCount, &Checksum);
					(*callback)(Address++, Data, userdata);
				}
				SREC_get_byte(&p, &ByteCount, &Checksum);
				if(Checksum ) { DIE(); }	//Checksum が不正
				if(ByteCount) { DIE(); }	//ByteCountが不正
			}
			break;
		case 4:
			//This record is reserved.
			/* FALLTHRU */
		case 5:
			//This optional record contains a 16-bit count of S1 / S2 / S3 records.
			//This record is used if the record count is less than or equal to 65,535 (0xFFFF), otherwise S6 record would be used.
			/* FALLTHRU */
		case 6:
			//This optional record contains a 24-bit count of S1 / S2 / S3 records.
			//This record is used if the record count is less than or equal to 16,777,215 (0xFFFFFF).
			//If less than 65,536 (0x010000), then S5 record would be used.
			//NOTE: This newer record is the most recent change (not sure if official).
			/* FALLTHRU */
		case 7:
			//This record contains the starting execution location at a 32-bit address.
			//This is used to terminate a series of S3 records.
			//If a SREC file is only used to program a memory device and the execution location is ignored, then an address of zero could be used.
			/* FALLTHRU */
		case 8:
			//This record contains the starting execution location at a 24-bit address.
			//This is used to terminate a series of S2 records.
			//If a SREC file is only used to program a memory device and the execution location is ignored, then an address of zero could be used.
			/* FALLTHRU */
		case 9:
			//This record contains the starting execution location at a 16-bit address.
			//This is used to terminate a series of S1 records.
			//If a SREC file is only used to program a memory device and the execution location is ignored, then an address of zero could be used.
			/* FALLTHRU */
		case 0:
			//This record contains vendor specific ASCII text represented as a series of hex digit pairs.
			//It is common to see the data for this record in the format of a null-terminated string.
			//The text data can be anything including a mixture of the following information:
			//file/module name, version/revision number, date/time, product name, vendor name, memory designator on PCB, copyright notice.
			/** no job **/
			break;
		}
	}
	fclose(fp);
	free(glbuf);	//getline()用バッファを開放する。
}
/****************************************************************************
 *	ローカル関数
 ****************************************************************************/
static int SREC_get_byte(const char** pp, uint8_t* pByteCount, uint8_t* pChecksum) {
	int i, c, Data = 0;
	if(!(*pByteCount)--) { DIE(); }	//ByteCountが不正
	for(i = 0; i < 2; i++) {
		c = *(*pp)++;
		     if((c >= '0') && (c <= '9')) { c -=  '0';       }
		else if((c >= 'A') && (c <= 'F')) { c -= ('A' - 10); }
		else                              { DIE();           }	//Dataが不正
		Data = (Data << 4) | c;
	}
	*pChecksum += Data;
	return Data;
}
