/*
 *	clipbakm.c
 *
 *	バックアップ管理
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2015-2016 Naoyuki Sawa
 *
 *	* Sun May 03 17:00:13 JST 2015 Naoyuki Sawa
 *	- 1st リリース。
 *	  主に、SVD(電源電圧低下検出)割り込みでのバックアップを想定しました。
 *	- SVD割り込みを使用せずに、アプリケーション終了時のバックアップにも使用できます。
 *	  P/ECEで利用する場合は、後者の使用方法になる事の方が多いと思います。
 *	  その場合は、メインループでBakMgr_Exec()を呼び出す必要は有りません。
 *	  アプリケーション終了時に、BakMgr_Exec(),BakMgr_Commit()の順で呼び出して下さい。
 *	* Mon May 04 15:54:46 JST 2015 Naoyuki Sawa
 *	- 「アプリケーションプログラムの使用例」を追記しました。
 *	* Wed May 06 10:25:34 JST 2015 Naoyuki Sawa
 *	- 前回の版は、割り込みルーチンがメモリ内容を変更途中である値を、バックアップしてしまう可能性が有りました。
 *	  詳細については、前回のアーカイブ内のBakMgr_Exec()実装部のコメント「原子性の保証について」を参照してください。
 *	  今回の版で修正したので、どのようなタイミングでSVD割り込み(NMI)が発生しても、安定したメモリ内容をバックアップします。
 *	  具体的には、コメント「原子性の保証について」の、対策案①を採用しました。
 *	  コメント「原子性の保証について」では対策案②を採用する予定と書きましたが、再検討して対策案①を採用する事にしました。
 *	  理由は、メモリを4KB多く消費するデメリットよりも、割り込み応答性能を維持する事の方が、重要だと思ったからです。
 *	* Sun Sep 04 21:41:17 JST 2016 Naoyuki Sawa
 *	- BakMgr_Alloc()が、(int)でアドレス演算を行っていたのを、(intptr_t)で行うように修正しました。
 *	  64ビット対応のための準備ですが、32ビットでも(intptr_t)で行う方が本当は正しいからです。
 *	  とは言え、32ビットではどちらでも同じ結果になるので、既存の32ビットコードには影響有りません。
 */
#include "clip.h"
/****************************************************************************
 *	ローカル変数
 ****************************************************************************/
static ST_BakMgr stBakMgr;
/****************************************************************************
 *	ローカル関数
 ****************************************************************************/
static int BakMgr_CalcCheckSum(const uint32_t* pSrc);
/****************************************************************************
 *	
 ****************************************************************************/
//バックアップ管理モジュールを初期化する。初期化フェーズの早い段階で呼び出せ。
int BakMgr_Init(void (*fnReadFile)(uint32_t Buffer[/*BakMgr_FileWords*/]), int MagicNumber) {
	//構造体をクリアする。
	memset(&stBakMgr, 0, sizeof stBakMgr);
	//バックアップファイルを読み出し、スナップショットA面に格納する。
	(*fnReadFile)(stBakMgr.Snapshot[0/*A面*/]);
	//スナップショットA面の先頭ワードに、マジックナンバーを加算する。
	stBakMgr.Snapshot[0/*A面*/][0] += MagicNumber;
	//スナップショットA面全体のチェックサムが0になったら、バックアップ有効である。
	//スナップショットA面全体のチェックサムが0でなければ、バックアップ無効である。
	if(!BakMgr_CalcCheckSum(stBakMgr.Snapshot[0/*A面*/])) { stBakMgr.Flag.Valid = 1; }
	//スナップショットA面の先頭ワードに、マジックナンバーを格納する。
	stBakMgr.Snapshot[0/*A面*/][0]  = MagicNumber;
	//スナップショットB面にも、A面と同じ内容をコピーする。
	memcpy(stBakMgr.Snapshot[1/*B面*/], stBakMgr.Snapshot[0/*A面*/], BakMgr_FileWords * 4);
	//バックアップ有効ならば1，バックアップ無効ならば0を返す。
	return stBakMgr.Flag.Valid;
}
/*--------------------------------------------------------------------------*/
//バックアップ対象とする揮発性RAMの内容を登録し、バックアップデータを取得する。アプリケーションのメインループが開始する前に各モジュールから呼び出せ。
int BakMgr_Alloc(void* VramAddr, int VramWords, int bRestore) {
	int iCh, iWord;
	//バックアップ対象とする揮発性RAMの先頭アドレスが、NULLではなく、ワード境界に整列している事を確認する。
	//尚、VramWords=0を指定する事は可能なので、検査しません。バックアップ領域が無いことを明示的に示します。
//{{2016/09/04変更:BakMgr_Alloc()が、(int)でアドレス演算を行っていたのを、(intptr_t)で行うように修正しました。
//	if(!VramAddr || ((int)VramAddr & 3)) { DIE(); }	//アプリケーションプログラムのバグ
//↓2016/09/04変更:BakMgr_Alloc()が、(int)でアドレス演算を行っていたのを、(intptr_t)で行うように修正しました。
	if(!VramAddr || ((intptr_t)VramAddr & 3)) { DIE(); }	//アプリケーションプログラムのバグ
//}}2016/09/04変更:BakMgr_Alloc()が、(int)でアドレス演算を行っていたのを、(intptr_t)で行うように修正しました。
	//バックアップチャンクテーブルの、最初の未使用エントリを探す。
	iWord = 1/*先頭に格納するチェックサムの分*/;	//ここに使用中エントリの合計ワード数を求める。
	for(iCh = 0; iCh <= BakMgrCh_Max; iCh++) {
		//未使用エントリを見つけたら、抜ける。
		if(!stBakMgr.TBL_BakMgrCh[iCh].VramAddr) { break; }
		//既に登録されている使用中エントリと重複する範囲を登録しようとしたら、エラー停止する。
		if((((uint32_t*)VramAddr                ) >= (stBakMgr.TBL_BakMgrCh[iCh].VramAddr                                           )) &&
		   (((uint32_t*)VramAddr                ) <= (stBakMgr.TBL_BakMgrCh[iCh].VramAddr + stBakMgr.TBL_BakMgrCh[iCh].VramWords - 1))) { DIE(); }
		if((((uint32_t*)VramAddr + VramWords - 1) >= (stBakMgr.TBL_BakMgrCh[iCh].VramAddr                                           )) &&
		   (((uint32_t*)VramAddr + VramWords - 1) <= (stBakMgr.TBL_BakMgrCh[iCh].VramAddr + stBakMgr.TBL_BakMgrCh[iCh].VramWords - 1))) { DIE(); }
		//使用中エントリの合計ワード数を加算する。
		iWord += stBakMgr.TBL_BakMgrCh[iCh].VramWords;
	}
	if(iCh > BakMgrCh_Max) { DIE(); }			//未使用エントリが見つからなかった場合はここで停止する。BakMgrCh_Maxを増やせ。
	if((iWord + VramWords) > BakMgr_FileWords) { DIE(); }	//バックアップサイズが超過した場合はここで停止する。BakMgr_FileWordsを増やせ。
	//バックアップチャンクを登録する。
	stBakMgr.TBL_BakMgrCh[iCh].VramAddr  = VramAddr;	//バックアップ対象とする揮発性RAMの先頭アドレス
	stBakMgr.TBL_BakMgrCh[iCh].VramWords = VramWords;	//バックアップ対象とする揮発性RAMのワード数
	//リストアフラグが指定されており,かつ,初期化時にバックアップが有効だったならば、バックアップ対象の揮発性RAMにバックアップデータをコピーする。
	//リストアフラグが指定されていないか、初期化時にバックアップが無効だったならば、バックアップ対象の揮発性RAMを変更しない。アプリケーションが明示的にクリア,又は,初期化せよ。
	//リストアフラグに0を指定する用途は、アプリケーション自身が新規起動であることを認識していて、バックアップチャンクの登録のみ行い、バックアップからの復元を試みない場合です。
	if(!stBakMgr.Flag.Valid) { bRestore = 0; }
	if(bRestore) { memcpy(VramAddr, &stBakMgr.Snapshot[stBakMgr.Flag.Side/*有効面*/][iWord], VramWords * 4); }
	//バックアップ対象の揮発性RAMにバックアップデータをコピーした場合は1，コピーしなかった場合は0を返す。
	return bRestore;
}
/*--------------------------------------------------------------------------*/
//バックアップ対象とする揮発性RAMの内容を、スナップショットの無効面にコピーした後、スナップショットの有効面を切り替える。アプリケーションのメインループから呼び出せ。
void BakMgr_Exec() {
	//バックアップ対象とする揮発性RAMの内容を、スナップショットの無効面にコピーする。
	uint32_t* pDest = &stBakMgr.Snapshot[(stBakMgr.Flag.Side ^ 1)/*無効面*/][1/*先頭に格納するチェックサムの分*/];
	int iCh;
	for(iCh = 0; iCh <= BakMgrCh_Max; iCh++) {
	 //任意	if(!stBakMgr.TBL_BakMgrCh[iCh].VramAddr) { break; }	//未使用エントリ以降は(VramWords=0)なので、処理を続けても抜けても結果は同じ。
ENTER_CS;//バックアップチャンク単位での原子性を保証するために割り込みを制御する。SVD割り込み(NMI)は禁止出来ないが、まだスナップショットの有効面は切り替えていないので、コピー中にSVD割り込みが発生しても安全です。
#ifdef  __GNUC__
		pDest = mempcpy(pDest,
		                stBakMgr.TBL_BakMgrCh[iCh].VramAddr,
		                stBakMgr.TBL_BakMgrCh[iCh].VramWords * 4);
#else //__GNUC__
		pDest  = memcpy(pDest,
		                stBakMgr.TBL_BakMgrCh[iCh].VramAddr,
		                stBakMgr.TBL_BakMgrCh[iCh].VramWords * 4);
		pDest +=        stBakMgr.TBL_BakMgrCh[iCh].VramWords;
#endif//__GNUC__
LEAVE_CS;//バックアップチャンク単位での原子性を保証するために割り込みを制御する。SVD割り込み(NMI)は禁止出来ないが、まだスナップショットの有効面は切り替えていないので、コピー中にSVD割り込みが発生しても安全です。
	 //ここで割り込みを受付可能なので、極端に長期間の割り込み禁止にはならず、割り込み応答性能の低下は少ないはずである。
	}
	//コピーが完了したら、スナップショットの有効面を切り替える。
	stBakMgr.Flag.Side ^= 1;
}
/*--------------------------------------------------------------------------*/
//スナップショットの有効面の内容を、バックアップファイルに書き込む。SVD割り込みから呼び出せ。
void BakMgr_Commit(void (*fnWriteFile)(const uint32_t Buffer[/*BakMgr_FileWords*/])) {
	uint32_t* const pSnapshot = stBakMgr.Snapshot[stBakMgr.Flag.Side/*有効面*/];
	const int MagicNumber = pSnapshot[0];
	//スナップショットの有効面の先頭ワードに、チェックサムを格納する。
	pSnapshot[0] = BakMgr_CalcCheckSum(pSnapshot);
	//スナップショットの有効面の内容を、バックアップファイルに書き込む。
	// - この処理はアトミックに行う事。現在の実装は、バックアップファイルの書き込み途中で処理が停止する事は考慮していない。
	//   もし考慮する場合は、バックアップファイルを二面用意する等の工夫が必要です。
	(*fnWriteFile)(pSnapshot);
	//スナップショットの有効面の先頭ワードに、マジックナンバーを復元する。
	// - 万一、SVD割り込みが複数回発生した場合に、BakMgr_Commit()が複数回呼び出される恐れがある。
	//   その場合も、二回目のBakMgr_Commit()で正しいチェックサムが求まるよう、スナップショットの先頭ワードにマジックナンバーを復元しておく事にした。
	//   SVD割り込みサービスルーチンか、SVD割り込みが複数回発生しないように考慮されていれば、この処理は無駄であるが、有っても害は無い。
	pSnapshot[0] = MagicNumber;
}
/*--------------------------------------------------------------------------*/
//スナップショットの指定された面全体のチェックサムを求める。この関数はSVD割り込みの中からも呼び出されるので特に高速化する必要が有る。
//チェックサムは、スナップショットの先頭ワードに格納されているマジックナンバーも含めたスナップショット全体の減算値とする。
//詳細は、後述の「チェックサムのロジックの説明」を参照せよ。
static int BakMgr_CalcCheckSum(const uint32_t* pSrc) {
	int CheckSum = 0;
	int iWord = (BakMgr_FileWords / 16);			//┐
	do {							//│
		CheckSum -= *pSrc++; CheckSum -= *pSrc++;	//│
		CheckSum -= *pSrc++; CheckSum -= *pSrc++;	//│
		CheckSum -= *pSrc++; CheckSum -= *pSrc++;	//│
		CheckSum -= *pSrc++; CheckSum -= *pSrc++;	//├高速化するために16ワード単位で計算する事にした。BakMgr_FileWordsの定義も16の倍数とせよ。
		CheckSum -= *pSrc++; CheckSum -= *pSrc++;	//│
		CheckSum -= *pSrc++; CheckSum -= *pSrc++;	//│
		CheckSum -= *pSrc++; CheckSum -= *pSrc++;	//│
		CheckSum -= *pSrc++; CheckSum -= *pSrc++;	//│
	} while(--iWord);					//┘
	return CheckSum;
}
/****************************************************************************
 *	チェックサムのロジックの説明
 ****************************************************************************/
//初回BakMgr_Init()にてMagicNumber=1の場合:
//│Snapshot[Side][0]=1
//│Snapshot[Side][1]=0
//│Snapshot[Side][2]=0
//│Snapshot[Side][4]=0
//SVD割り込み時BakMgr_Commit()にてData={2,3,4}の場合:
//│Snapshot[Side][0]=1
//│Snapshot[Side][1]=2
//│Snapshot[Side][2]=3
//│Snapshot[Side][4]=4
//BakMgr_CalcCheckSum()=-10:
//│Snapshot[Side][0]=-10 ┐
//│Snapshot[Side][1]=2   │
//│Snapshot[Side][2]=3   ├→バックアップファイルへ
//│Snapshot[Side][4]=4   ┘
//復帰BakMgr_Init()にて:
//│Snapshot[Side][0]=-10 ⇒ +MagicNumber ⇒ -9
//│Snapshot[Side][1]=2
//│Snapshot[Side][2]=3
//│Snapshot[Side][4]=4
//BakMgr_CalcCheckSum()=0 ⇒ バックアップ有効
/****************************************************************************
 *	アプリケーションプログラムの使用例① - 基本的な使い方
 ****************************************************************************/
//原則は、BakMgr_Init()⇒{BakMgr_Alloc()×モジュール数}⇒{BakMgr_Exec()×繰り返し}⇒BakMgr_Commit()の順で実行する。
//#ifdef  __TIMESTAMP__
//static const char TimeStamp[]=__TIMESTAMP__;        //Visual C++ 6.0
//#else //__TIMESTAMP__
//static const char TimeStamp[]=__DATE__ " " __TIME__;//P/ECE開発環境のgcc33
//#endif//__TIMESTAMP__
//static void fnReadFile(       uint32_t Buffer[/*BakMgr_FileWords*/]) {
//	FILE* fp=fopen("backup.bin","rb");
//	if(!fp) { return; }
//	fread( Buffer,4,BakMgr_FileWords,fp);
//	fclose(fp);
//}
//static void fnWriteFile(const uint32_t Buffer[/*BakMgr_FileWords*/]) {
//	FILE* fp=fopen("backup.bin","wb");
//	if(!fp) { return; }
//	fwrite(Buffer,4,BakMgr_FileWords,fp);
//	fclose(fp);
//}
//static void svd_isr() {
//	BakMgr_Commit(fnWriteFile);
//	exit(0);
//}
//struct {
//	int     :0; //+ 0  0 xをワード境界から始める
//	char x[11]; //+ 0,11
//	int     :0; //+11, 1 xをワード単位に拡張し、yをワード境界から始める
//	char y[22]; //+12,22
//	int     :0; //+34, 2 yをワード単位に拡張する
//} st;
//void app_main() {
//	//--- init ---
//	BakMgr_Init(fnReadFile,one_at_a_time_hash(TimeStamp,sizeof TimeStamp));
//	BakMgr_Alloc(st.x,(sizeof st.x+3)/4,1);//VramWordsをワード単位で指定する事に注意せよ
//	BakMgr_Alloc(st.y,(sizeof st.y+3)/4,1);//VramWordsをワード単位で指定する事に注意せよ
//	//--- loop ---
//	for(;;) {
//		schedule();
//		surface_clear(&surface,0);
//		if(joy&TRG_A) { svd_isr(); }//'SVD割り込み'の代り
//		sprintf(st.x,"%d",atoi(st.x)+1);
//		sprintf(st.y,"%d",atoi(st.y)+2);
//		render_printf(&render,0,0,1,3,"%s\n%s",st.x,st.y);
//		BakMgr_Exec();
//	}
//}
/****************************************************************************
 *	アプリケーションプログラムの使用例② - 遅延復帰のテクニック1
 ****************************************************************************/
//一部のモジュールで、BakMgr_Exec()よりも後に、BakMgr_Alloc()を呼ぶようにする事も出来る。
//そのようにしたモジュールは、特定の条件が成立するまではリセット起動後の状態で動作し、特定の条件が成立してBakMgr_Alloc()を呼んだ後は復帰起動の状態で動作する。
//そのようにしたモジュールの、BakMgr_Alloc()を呼ぶ前にSVD割り込みが発生した場合は、それらのモジュールについては前回のバックアップ内容が維持される。
//void app_main() {
//	//--- init ---
//	BakMgr_Init(fnReadFile,one_at_a_time_hash(TimeStamp,sizeof TimeStamp));
//	BakMgr_Alloc(st.x,(sizeof st.x+3)/4,1);//VramWordsをワード単位で指定する事に注意せよ
//	//--- loop ---
//	for(;;) {
//		schedule();
//		surface_clear(&surface,0);
//		if(joy&TRG_A) { svd_isr(); }//'SVD割り込み'の代り
//		if(joy&TRG_B) { BakMgr_Alloc(st.y,(sizeof st.y+3)/4,1); }//'特定の条件が成立'の代り｡1回だけ実行可能｡
//		sprintf(st.x,"%d",atoi(st.x)+1);
//		sprintf(st.y,"%d",atoi(st.y)+2);
//		render_printf(&render,0,0,1,3,"%s\n%s",st.x,st.y);
//		BakMgr_Exec();
//	}
//}
/****************************************************************************
 *	アプリケーションプログラムの使用例③ - 遅延復帰のテクニック2
 ****************************************************************************/
//全てのモジュールで、BakMgr_Exec()よりも後に、BakMgr_Alloc()を呼ぶようにする事も出来る。
//void app_main() {
//	//--- init ---
//	BakMgr_Init(fnReadFile,one_at_a_time_hash(TimeStamp,sizeof TimeStamp));
//	//--- loop ---
//	for(;;) {
//		schedule();
//		surface_clear(&surface,0);
//		if(joy&TRG_A) { svd_isr(); }//'SVD割り込み'の代り
//		if(joy&TRG_B) {//'特定の条件が成立'の代り｡1回だけ実行可｡
//			BakMgr_Alloc(st.x,(sizeof st.x+3)/4,1);
//			BakMgr_Alloc(st.y,(sizeof st.y+3)/4,1); }
//		sprintf(st.x,"%d",atoi(st.x)+1);
//		sprintf(st.y,"%d",atoi(st.y)+2);
//		render_printf(&render,0,0,1,3,"%s\n%s",st.x,st.y);
//		BakMgr_Exec();
//	}
//}
