/*	
 *	clipsrf.c
 *
 *	ld33互換ローダ
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2009 Naoyuki Sawa
 *
 *	* Mon Jul 13 18:26:06 JST 2009 Naoyuki Sawa
 *	- 1st リリース。
 *	  ld33の仕様は、EPSON開発ツール(ccEVv40.exe)のドキュメント(/cc33/UTILITY/ld33)を参照してください。
 *	  同じものを、keep/ld33.7zにも入れてあります。
 */
#include "clip.h"

/*****************************************************************************
 *	
 *****************************************************************************/

/* srf33制御ヘッダ
 * - 通常のsrf33ファイルも、ローダ用srf33ファイルも、同じ構\造です。
 * - バイトオーダーはビッグエンディアンで、ワードアライメントされていません。
 */
typedef struct _sr_ctrlinfo {
	unsigned char c_fatt  [2];	/* + 0,2: ファイル制御フラグ */
	unsigned char c_pentry[2];	/* + 2,2: エントリーアドレス */
	unsigned char c_ver   [2];	/* + 4,2: srf33バージョン情報 */
	unsigned char c_scncnt[2];	/* + 6,2: セクション情報数 */
	unsigned char c_scnptr[4];	/* + 8,4: セクション情報チェーン */
	unsigned char c_debptr[4];	/* +12,4: デバッグ制御情報チェーン */
} sr_ctrlinfo;				/* =16 */

/* セクション情報
 * - 通常のsrf33ファイルも、ローダ用srf33ファイルも、同じ構\造です。
 * - バイトオーダーはビッグエンディアンで、ワードアライメントされていません。
 */
typedef struct _sr_scninfo {
	unsigned char s_nxptr [4];	/* + 0,4: 次のセクション情報へのチェーン */
	unsigned char s_scntyp[2];	/* + 4,2: セクションタイプ */
	unsigned char s_lnktyp[2];	/* + 6,2: リンク方法 */
	unsigned char s_scnatt[2];	/* + 8,2: セクション属性 */
	unsigned char s_off   [4];	/* +10,4: セクションのスタートアドレス */
	unsigned char s_rcptr [4];	/* +14,4: リロケーション情報チェーン */
	unsigned char s_rcsiz [4];	/* +18,4: リロケーション情報バイトサイズ */
	unsigned char s_exptr [4];	/* +22,4: エクスターン情報チェーン */
	unsigned char s_exsiz [4];	/* +26,4: エクスターン情報バイトサイズ */
	unsigned char s_excnt [4];	/* +30,4: エクスターン情報の個数 */
	unsigned char s_rdptr [4];	/* +34,4: 実データへのチェーン */
	unsigned char s_dsiz  [4];	/* +38,4: 実データバイトサイズ */
	unsigned char s_scnndx[2];	/* +42,2: セクションID */
} sr_scninfo;				/* =44 */

/* リロケーション情報 (ローダ用)
 * - 通常のsrf33ファイルと、ローダ用srf33ファイルでは、構\造が違っています。
 * - バイトオーダーはリトルエンディアンで、ワードアライメントされています。
 */
typedef struct _sr_rcinfo {
	unsigned short r_rctyp;		/* + 0,2: リロケーションタイプ */
	unsigned short r_scnndx;	/* + 2,2: セクションまたはエクスターン情報のインデックス */
	unsigned long  r_scnoff;	/* + 4,4: セクション内オフセット */
	         long  r_symoff;	/* + 8,4: リロケーション対象アドレスのオフセット値 */
} sr_rcinfo;				/* =12 */

/* エクスターン情報 (ローダ用)
 * - 通常のsrf33ファイルと、ローダ用srf33ファイルでは、構\造が違っています。
 * - バイトオーダーはリトルエンディアンで、ワードアライメントされています。
 */
typedef struct _sr_extinfo {
	unsigned long e_namoff;		/* + 0,4: シンボル名のオフセット */
} sr_extinfo;				/* = 4 */

/*****************************************************************************
 *	
 *****************************************************************************/

void
srf_init(void* srf, void* (*symbol)(const char* name, int user_data), int user_data)
{
	sr_ctrlinfo* ctrlinfo;
	sr_scninfo* scninfo[3]; /* [0] = code, [1] = data, [2] = bss */
	sr_scninfo* scnptr;
	sr_rcinfo* rcinfo;
	sr_rcinfo* rcinfo_end;
	sr_extinfo* extinfo;
	int i_scn;
	int dsiz;
	int addr;
	void* off;
	char* name;
	unsigned short* symoff;

	/* ローダ用srf33ファイルは、4バイト境界にロードしてください。
	 * ローダ用srf33ファイルの構\造は、各情報が4バイト境界にアライメントされる仕様ですが、
	 * 先頭アドレスのアライメントがずれていると、各情報のアライメントもずれてしまいます。
	 */
	if((int)srf & 3) {
		DIE();
	}

	/* srf33制御ヘッダを格納します。 */
	ctrlinfo = srf;

	/* ローダ用srf33ファイルであることを確認します。
	 * ローダ用srf33ファイル(.srf)は、lk33の"-ld"オプションを指定して、作成してください。
	 * 通常のsrf33ファイル(.srf)や、リンクしていないsrf33ファイル(.o)は、指定できません。
	 */
	if(!(GET_BEHALF(ctrlinfo->c_fatt) & 0x0020)) { /* ローダービット */
		DIE();
	}

	/* srf33バージョン情報を確認します。
	 * P/ECE開発環境のツールが、これ以外のバージョン情報を格納することは無いはずです。
	 */
	if(GET_BEHALF(ctrlinfo->c_ver) != 0x3300) { /* バージョン = 33, リビジョン = 00 */
		DIE();
	}

	/* ローダ用srf33ファイルは、セクション情報が3つ固定の仕様です。
	 * セクション情報1がcodeセクション、セクション情報2がdataセクション、セクション情報3がbssセクションです。
	 */
	if(GET_BEHALF(ctrlinfo->c_scncnt) != 3) { /* [0] = code, [1] = data, [2] = bss */
		DIE();
	}

	/* 以下のコード中で、voidポインタ型変数srfへの加算を多用していますが、GNU C拡張仕様です。
	 * 「フリーソ\フトウェア徹底活用講座(7)」より引用(http://www.cqpub.co.jp/interface/column/freesoft/2003/200303/1.htm)
	 *   ● voidポインタと関数へのポインタの算術演算
	 *   │ GNU Cでは、voidポインタや関数へのポインタで加算と減算の演算をすることが可能\です。
	 *   │ これは、voidや関数のサイズを1とみなして計算されます。
	 *   │ オプション-Wpointer-arithは、この拡張が使われた場合に警告を出します。
	 *   │ やはり通常は混乱の元になるので、voidならば想定される型にキャストするなりして、この拡張仕様は回避すべきです。
	 * “回避すべきです”と書かれていますが、便利なので使うことにしました。(srf + 〜のところ)
	 *  なお、/usr/PIECE/include/stddef.hも拡張仕様を使っており、オプション-Wpointer-arithを指定すると警告が多発します。
	 */
	for(i_scn = 0, scnptr = srf + GET_BEWORD(ctrlinfo->c_scnptr);
	    i_scn < 3;
	    i_scn++  , scnptr = srf + GET_BEWORD(scnptr->s_nxptr)) {

		/* セクション情報のポインタを格納します。 */
		scninfo[i_scn] = scnptr;

		/* ローダ用srf33ファイルの構\造は、セクション情報が4バイト境界にアライメントされる仕様です。
		 * もしここでアサートしたら、sr_ctrlinfo.c_scnptrかsr_scninfo.s_nxptrに、4の倍数でない値が格納されています。
		 * 仕様どおりならば、ここでアサートすることは有り得ませんが、念のために確認しておくことにします。
		 */
		if((int)scnptr & 3) {
			DIE();
		}

		/* ローダ用srf33ファイルは、セクション情報が3つ固定の仕様です。
		 * セクション情報1がcodeセクション、セクション情報2がdataセクション、セクション情報3がbssセクションです。
		 * 念のために、セクションタイプを検査して、上記の仕様どおりであることを確認します。
		 */
		if(GET_BEHALF(scnptr->s_scntyp) != (i_scn + 1)) { /* 1 = code, 2 = data, 3 = bss */
			DIE();
		}

		/* ローダ用srf33ファイルの構\造は、各情報が4バイト境界にアライメントされる仕様です。
		 * もしここでアサートしたら、リロケーション情報かエクスターン情報が4バイト境界にアライメントされていません。
		 * 仕様どおりならば、ここでアサートすることは有り得ませんが、念のために確認しておくことにします。
		 */
		if(GET_BEWORD(scnptr->s_rcptr) & 3) {
			DIE();
		}
		if(GET_BEWORD(scnptr->s_exptr) & 3) {
			DIE();
		}

		/* ローダ用srf33ファイルの構\造は、各情報が4バイト境界、実データが2バイト境界にアライメントされる仕様です。
		 * しかし、実際のデータを調査したところ、実データも4バイト境界にアライメントされているようです。
		 * 4バイト境界であることを利用して、codeセクションやdataセクションの実データを、そのままの位置で使います。
		 * 念のために、本当に実データが4バイト境界にアライメントされているかどうか確認することにします。
		 */
		if(GET_BEWORD(scnptr->s_rdptr) & 3) {
			DIE();
		}

		/* sr_scninfo.s_offフィールドに、セクションの実行時アドレスを格納することにします。 */
		dsiz = GET_BEWORD(scnptr->s_dsiz);
		if(dsiz) {
			/* 実データバイトサイズが0でなければ、そのセクションが存在します。 */
			if(i_scn < 2) { /* [0] = code, [1] = data */
				/* codeセクションかdataセクションならば、実データをそのままの位置で使います。 */
				off = srf + GET_BEWORD(scnptr->s_rdptr);
			} else { /* [2] = bss */
				/* bssセクションならば、ゼロクリアしたメモリを確保します。 */
				off = calloc(dsiz, 1);
				if(!off) {
					DIE();
				}
			}
		} else {
			/* 実データバイトサイズが0ならば、そのセクションは存在しません。
			 * codeセクション,dataセクション,bssセクションいずれも、そのセクションが存在しない可能\性があります。
			 */
			off = NULL;
		}
		PUT_BEWORD(scnptr->s_off, (int)off); /* セクションの実行時アドレスを格納 */
	}

	/* 各セクションについて、リロケーション処理を行ないます。 */
	for(i_scn = 0; i_scn < 3; i_scn++) {

		/* セクション情報のポインタを取得します。 */
		scnptr = scninfo[i_scn];

		/* リロケーション情報の先頭アドレスと、終端アドレスを取得します。 */
		rcinfo     = srf + GET_BEWORD(scnptr->s_rcptr);
		rcinfo_end = srf + GET_BEWORD(scnptr->s_rcptr) + GET_BEWORD(scnptr->s_rcsiz);

		/* 各リロケーション情報について… */
		while(rcinfo < rcinfo_end) {

			/* リロケーションタイプの上位8ビットが、セクション参照か、エクスターン情報参照かを示しています。 */
			switch(rcinfo->r_rctyp >> 8) {
			case 0: /* セクション参照 */
				/* セクション参照インデクスが、範囲内であることを確認します。
				 * - 通常のsrf33ファイルと、ローダ用srf33ファイルで、sr_rcinfo.r_scnndxの意味が異なることに注意してください。
				 *     通常のsrf33ファイルでは、「参照するエクスターン情報が所属する*セクションのID*」を指しています。
				 *     ローダ用srf33ファイルでは、「セクションまたはエクスターン情報の*インデックス*」を指しています。
				 *   通常のsrf33ファイルでは、sr_scninfo.s_scnndxを検索する必要がありますが、ローダ用srf33ファイルでは、その必要が有りません。
				 * - 実際のデータを調査してみると、少なくともローダ用srf33ファイルにおいては、セクションの順番とセクションIDは一致しています。
				 *   すなわち、「scninfo[0]->s_scnndx == 0, scninfo[1]->s_scnndx == 1, scninfo[2]->s_scnndx == 2」となっています。
				 *   従って、ローダ用srf33ファイルにおいてsr_rcinfo.r_scnndxがセクションIDを指していると考えても、結果は同じです。
				 * - インデックスと見なして実装する方が簡単なので、以下のコードはそのように実装しました。
				 *   EPSONの「ld33.c」ソ\ースも、インデックスと見なして実装されているので、この方法で安全だと思います。
				 */
				if(rcinfo->r_scnndx > 2) { /* [0] = code, [1] = data, [2] = bss */
					DIE();
				}
				/* セクションの実行時アドレスを取得します。 */
				addr = GET_BEWORD(scninfo[rcinfo->r_scnndx]->s_off);
				break;
			case 1: /* エクスターン情報参照 */
				/* エクスターン情報の先頭アドレスと、終端アドレスを取得します。
				 * エクスターン情報の終端アドレスが、オフセットの起点アドレスとなります。
				 */
				extinfo =             srf + GET_BEWORD(scnptr->s_exptr);
				name    = (char*)(extinfo + GET_BEWORD(scnptr->s_excnt));
				/* エクスターン情報参照インデクスが、範囲内であることを確認します。 */
				if(rcinfo->r_scnndx > GET_BEWORD(scnptr->s_excnt) - 1) {
					DIE();
				}
				/* 外部参照するシンボル名を取得します。 */
				extinfo +=  rcinfo->r_scnndx;
				name    += extinfo->e_namoff;
				/* symbolサーチ関数が指定されていなければ、エラーとします。 */
				if(!symbol) {
					DIE();
				}
				/* symbolに対応する実行時アドレスを取得します。 */
				addr = (int)(*symbol)(name, user_data);
				break;
			default:
				DIE();
			}

			/* リロケーション対象アドレスのオフセット値を加算し、アドレスを決定します。 */
			addr += rcinfo->r_symoff;

			/* アドレスを格納するセクション内オフセットを取得し、実行時アドレスを決定します。 */
			symoff = (unsigned short*)(GET_BEWORD(scnptr->s_off) + rcinfo->r_scnoff);

			/* リロケーションタイプの下位8ビットが、アドレスを格納する方法を示しています。
			 * - リロケーションタイプ2と7は、通常のsrf33ファイルとローダ用srf33ファイルとで、意味が異なることに注意してください。
			 *   通常のsrf33ファイルの場合、@rh,@rm,@rlや、@h,@m,@lごとに、ひとつひとつリロケーション情報が生成されます。
			 *   ローダ用srf33ファイルでは、@rhと@hだけが生成され、@rhが後続の@rm,@rlを、@hが後続の@m,@lを含むものと見なします。
			 *   このように変更されている理由は、リロケーション情報のサイズを節約するためと、リロケーション処理の簡略化のためと思います。
			 */
			switch(rcinfo->r_rctyp & 255) {
			case 2: /* 32bit相対シンボル(31:22),SYMBOL@rh */
				/* セクション内オフセットのアライメントが仕様に明記されていませんが、CPUの特性上、明らかに2バイト境界となるはずです。 */
				if((int)symoff & 1) {
					DIE();
				}
				addr -= (int)&symoff[2]; /* callやjp命令の位置からの相対アドレスとします */
				symoff[0] = (symoff[0] & 0xE000) | ((addr >> 19) & 0x1FF8); /* ext  SYMBOL@rh */
				symoff[1] = (symoff[1] & 0xE000) | ((addr >>  9) & 0x1FFF); /* ext  SYMBOL@rm */
				symoff[2] = (symoff[2] & 0xFF00) | ((addr >>  1) & 0x00FF); /* call SYMBOL@rl */
				break;
			case 7: /* 32bit絶対シンボル(31:19),SYMBOL+imm32@h */
				/* セクション内オフセットのアライメントが仕様に明記されていませんが、CPUの特性上、明らかに2バイト境界となるはずです。 */
				if((int)symoff & 1) {
					DIE();
				}
				symoff[0] = (symoff[0] & 0xE000) | ((addr >> 19) & 0x1FFF); /* ext      SYMBOL+imm32@h */
				symoff[1] = (symoff[1] & 0xE000) | ((addr >>  6) & 0x1FFF); /* ext      SYMBOL+imm32@m */
				symoff[2] = (symoff[2] & 0xFC0F) | ((addr <<  4) & 0x03F0); /* ld.w %rd,SYMBOL+imm32@l */
				break;
			case 10: /* 32bit絶対シンボル(31:0),SYMBOL */
				/* セクション内オフセットのアライメントが仕様に明記されていませんが、CPUの特性上、明らかに4バイト境界となるはずです。 */
				if((int)symoff & 3) {
					DIE();
				}
				*(int*)symoff = addr;
				break;
			default:
				DIE();
			}

			/* 次のリロケーション情報へ。 */
			rcinfo++;
		}
	}
}

/*---------------------------------------------------------------------------*/

void
srf_free(void* srf)
{
	/* bssセクションのために、確保したメモリを開放します。
	 * bssセクションが無かった場合は、free(NULL)となるので安全です。
	 */
	free(srf_bss(srf));
}

/*---------------------------------------------------------------------------*/

/* セクションの実行時アドレスを取得します。 */
static void*
srf_code_data_bss(void* srf, int i/*0=code,1=data,2=bss */)
{
	sr_ctrlinfo* ctrlinfo;
	sr_scninfo* scnptr;

	ctrlinfo = srf;
	scnptr = srf + GET_BEWORD(ctrlinfo->c_scnptr);
	while(i--) {
		scnptr = srf + GET_BEWORD(scnptr->s_nxptr);
	}

	return (void*)GET_BEWORD(scnptr->s_off);
}

void*
srf_code(void* srf)
{
	return srf_code_data_bss(srf, 0/*code*/);
}

void*
srf_data(void* srf)
{
	return srf_code_data_bss(srf, 1/*data*/);
}

void*
srf_bss(void* srf)
{
	return srf_code_data_bss(srf, 2/*bss*/);
}

