/*	
 *	clipidle.c
 *
 *	アイドルスレッド
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2009 Naoyuki Sawa
 *
 *	* Fri Jun 19 01:44:03 JST 2009 Naoyuki Sawa
 *	- 1st リリース。
 *	* Sun Jun 21 21:42:18 JST 2009 Naoyuki Sawa
 *	- clippce.cにapptm_wk変数を追加したことに伴い、残りタイムスライスの算出にapptm_wkを参照するよう変更しました。
 *	* Sun Oct 11 21:57:48 JST 2009 Naoyuki Sawa
 *	- idle_stat()関数を追加しました。
 *	* Mon Oct 12 05:55:33 JST 2009 Naoyuki Sawa
 *	- idle_exit()関数を追加しました。
 *	- idle_exit()関数の追加に伴い、idle_stub()が不要になったため削除しました。
 */
#include "clip.h"

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

/* * Thu Jun 18 23:22:52 JST 2009 Naoyuki Sawa
 * - 簡単のために、アイドルスレッドのスタックサイズは固定とすることにしました。
 *   idle_init()でスタックサイズを指定することも考えましたが、アイドルスレッドはひとつしか走らないので、
 *   多少、スタックサイズに無駄があっても大きな影響は無いと考え、充分な大きさである8KBに固定としました。
 * - アプリケーションに本モジュールをリンクすると、アイドルスレッドを起動しているかどうかにかかわらず、
 *   固定でメモリを消費してしまうので、必要の無いアプリケーションにはリンクしないよう注意してください。
 */
#define IDLE_STACK_SIZE 8192 /* 調整可 */

static int idle_stack[IDLE_STACK_SIZE / sizeof(int)];	/* アイドルスレッドのスタック */
static int idle_exit_code;				/* アイドルスレッドの終了コード */
static int idle_count;					/* アイドルスレッドの残りタイムスライス */
static int* idle_sp;					/* アイドルスレッドのスタックポインタ */
static int* main_sp __attribute__((unused));		/* メインスレッドのスタックポインタ */

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

/* * Thu Jun 18 20:15:58 JST 2009 Naoyuki Sawa
 * - idle_context_switcher()の中で、多重割り込み中はプリエンプトさせない理由について、以下に説明します。
 * - P/ECEカーネルの中で、多重割り込みを使っているのは、usr/PIECE/sysdev/pcekn/snd.c:sound_isr3()です。
 *   多重割り込み中の不安定な状態のまま、別のスレッドに長時間コンテキストスイッチすることは危険です。
 *   pceTimerSetContextSwitcher()には、PCE_TSCSF_INCRITICALフラグを用いて、これを防ぐ仕組みが有ります。
 * - CLiPライブラリの中で、多重割り込みを使っているのは、clipldir.c:_ldirect_isr()です。(2006/06/18現在)
 *   (ちなみに多重割り込みを使っている理由は、_ldirect_isr()から呼び出すldirect_trans()が時間がかかり、
 *    割り込み禁止のままだとサウンド割り込みが長時間ブロックされて、ノイズが発生するのを防ぐためです。)
 * - _ldirect_isr()が多重割り込み中であることを示すために、PCE_TSCSF_INCRITICALフラグに相当するような
 *   グローバルフラグを用いて、idle_context_switcher()に知らせることもできますが、もしそうすると、
 *   clipldir.cモジュールと本モジュールの間に依存性が生じてしまい、あまり良い設計とは言えません。
 *   そこで、clipldir.cモジュールには手を加えず、idle_context_switcher()自身で判断する方法を考えました。
 * - 通常の割り込み許可状態は、%psr[IL]=0、かつ、%psr[IE]=1です。
 *   割り込みが発生したとき、%psr[IL]=発生した割り込みレベル、かつ、%psr[IE]=0となります。
 *   その割り込みの中で、多重割り込みを受け付ける場合は、%psr[IL]=受け付ける割り込みレベル-1、かつ、
 *   %psr[IE]=1とします。このとき、%psr[IL]=0とすることはありえません。
 *   なぜなら、そうすると割り込み許可状態と同じになってしまい、多重割り込みの意味が無くなるからです。
 * - 以上より、idle_context_switcher()が呼ばれたときに、多重割り込みかどうかを判断するには、
 *   スタックに保存されている%psrの、ILが0か否かを判断し、0ならば通常の割り込み許可状態からの割り込み、
 *   0以外ならば、多重割り込み受け付け状態からの多重割り込みと判断できます。
 * - なお、%psr[IL]を見て多重割り込みかどうかを判断する方法で、サウンドの多重割り込みも判断できます。
 *   PCE_TSCSF_INCRITICALを検査しなくても、%psr[IL]を見る方法だけでも、結果的には同じになります。
 *   しかし、PCE_TSCSF_INCRITICALを検査することは、pceTimerSetContextSwitcher()の仕様ですので、
 *   形式上、明示的にPCE_TSCSF_INCRITICALを検査して判断するコードも残しておくことにしました。
 */
/*static*/ unsigned long idle_context_switcher(unsigned long nowsp, int flag);
asm("
		.code
		.align	1
idle_context_switcher:
		;// %r12     := nowsp
		;// %r13     := flag
		;// [%sp+ 0] := retp ─ usr/PIECE/sysdev/pcekn/timer.c:timer_isr()への戻りポインタ
		;// [%sp+ 4] := %alr ┬ pceTimerSetContextSwitcher()「スタックフレームの構\成」仕様
		;// [%sp+ 8] := %ahr │
		;// [%sp+12] := %r0  │
		;// [%sp+16] := %r1  │
		;// [%sp+20] := %r2  │
		;// [%sp+24] := %r3  │
		;// [%sp+28] := %r4  │
		;// [%sp+32] := %r5  │
		;// [%sp+36] := %r6  │
		;// [%sp+40] := %r7  │
		;// [%sp+44] := %r8  │
		;// [%sp+48] := %r9  │
		;// [%sp+52] := %r10 │
		;// [%sp+56] := %r11 │
		;// [%sp+60] := %r12 │
		;// [%sp+64] := %r13 │
		;// [%sp+68] := %r14 │
		;// [%sp+72] := %r15 │
		;// [%sp+76] := %psr │
		;// [%sp+80] := %pc  ┴
		xld.w		%r4, [idle_count]		;// idle_count--
		sub		%r4, 1
		xld.w		[idle_count], %r4
		jrgt		idle_context_switcher_RET	;// if(idle_count <= 0) {
		and		%r13, 1				;//   if(!(flag & PCE_TSCSF_INCRITICAL)) {
		jrne		idle_context_switcher_RET
		xld.w		%r4, [%sp+76]			;//     %r4  := %psr
		xand		%r4, %r4, 0x00000F00		;//     if(%psr[IL] == 0) { /* 多重割り込み中でないこと */
		jrne		idle_context_switcher_RET
		xld.w		[idle_sp], %r12			;//       idle_sp := nowsp
		xld.w		%r12, [main_sp]			;//       nowsp   := main_sp
idle_context_switcher_RET:					;// } } }
		ret.d						;// return  nowsp
		ld.w		%r10, %r12			;// %r10 := nowsp	*delay*
");

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

//{{2009/10/12:idle_exit()追加に伴い、idle_stub()を削除
///*static*/ void idle_stub() __attribute__((noreturn));
//asm("
//		.code
//		.align		1
//idle_stub:
//		;// アイドル処理の関数を呼び出します。
//		popn		%r1			;// %r0  := fn,  %r1  :=    param
//		call.d		%r0			;// %r10 := exit_code := fn(param)
//		ld.w		%r12, %r1		;// %r12 :=                 param	*delay*
//		;// idle_yield()から呼ばれた場合、これ以降プリエンプトが発生しないよう、割り込み禁止。
//		ld.w		%psr, %r8		;// %psr[IE] := 0
//		;// アイドルスレッドの終了コードを格納し、アイドルスレッドのコンテキストを破棄します。
//		xld.w		[idle_exit_code], %r10	;// idle_exit_code := exit_code
//		xld.w		[idle_sp], %r8		;// idle_sp        := NULL
//		;// メインスレッドのコンテキストを復元します。
//		xld.w		%r0, [main_sp]		;// %r0 := main_sp
//		ld.w		%sp, %r0		;// %sp := main_sp
//		;// [%sp+ 0] := %alr ┬ pceTimerSetContextSwitcher()「スタックフレームの構\成」仕様
//		;// [%sp+ 4] := %ahr │
//		;// [%sp+ 8] := %r0  │
//		;// [%sp+12] := %r1  │
//		;// [%sp+16] := %r2  │
//		;// [%sp+20] := %r3  │
//		;// [%sp+24] := %r4  │
//		;// [%sp+28] := %r5  │
//		;// [%sp+32] := %r6  │
//		;// [%sp+36] := %r7  │
//		;// [%sp+40] := %r8  │
//		;// [%sp+44] := %r9  │
//		;// [%sp+48] := %r10 │
//		;// [%sp+52] := %r11 │
//		;// [%sp+56] := %r12 │
//		;// [%sp+60] := %r13 │
//		;// [%sp+64] := %r14 │
//		;// [%sp+68] := %r15 │
//		;// [%sp+72] := %psr │
//		;// [%sp+76] := %pc  ┴
//		popn		%r1
//		ld.w		%ahr, %r1
//		ld.w		%alr, %r0
//		popn		%r15
//		reti					;// idle_yield()から呼ばれた場合は、割り込み禁止のまま帰る。
//							;// idle_wait() から呼ばれた場合は、元の割り込み状態に戻る。
//");
//}}2009/10/12:idle_exit()追加に伴い、idle_stub()を削除
//→ * Mon Oct 12 05:55:33 JST 2009 Naoyuki Sawa
//→ - アイドル関数の終了処理をidle_exit()に分けたため、idle_stub()は不要になりました。

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

/*static*/ void idle_exec();
asm("
		.code
		.align	1
idle_exec:
		;// メインスレッドのコンテキストを退避します。
		xsub		%sp, %sp, 4
		pushn		%r15
		ld.w		%r0, %alr
		ld.w		%r1, %ahr
		pushn		%r1
		ld.w		%r0, %psr
		xld.w		[%sp+72], %r0
		;// [%sp+ 0] := %alr ┬ pceTimerSetContextSwitcher()「スタックフレームの構\成」仕様
		;// [%sp+ 4] := %ahr │
		;// [%sp+ 8] := %r0  │
		;// [%sp+12] := %r1  │
		;// [%sp+16] := %r2  │
		;// [%sp+20] := %r3  │
		;// [%sp+24] := %r4  │
		;// [%sp+28] := %r5  │
		;// [%sp+32] := %r6  │
		;// [%sp+36] := %r7  │
		;// [%sp+40] := %r8  │
		;// [%sp+44] := %r9  │
		;// [%sp+48] := %r10 │
		;// [%sp+52] := %r11 │
		;// [%sp+56] := %r12 │
		;// [%sp+60] := %r13 │
		;// [%sp+64] := %r14 │
		;// [%sp+68] := %r15 │
		;// [%sp+72] := %psr │
		;// [%sp+76] := %pc  ┴
		ld.w		%r0, %sp
		xld.w		[main_sp], %r0
		;// アイドルスレッドのコンテキストを復元します。
		xld.w		%r0, [idle_sp]
		ld.w		%sp, %r0
		;// [%sp+ 0] := %alr ┬ pceTimerSetContextSwitcher()「スタックフレームの構\成」仕様
		;// [%sp+ 4] := %ahr │
		;// [%sp+ 8] := %r0  │
		;// [%sp+12] := %r1  │
		;// [%sp+16] := %r2  │
		;// [%sp+20] := %r3  │
		;// [%sp+24] := %r4  │
		;// [%sp+28] := %r5  │
		;// [%sp+32] := %r6  │
		;// [%sp+36] := %r7  │
		;// [%sp+40] := %r8  │
		;// [%sp+44] := %r9  │
		;// [%sp+48] := %r10 │
		;// [%sp+52] := %r11 │
		;// [%sp+56] := %r12 │
		;// [%sp+60] := %r13 │
		;// [%sp+64] := %r14 │
		;// [%sp+68] := %r15 │
		;// [%sp+72] := %psr │
		;// [%sp+76] := %pc  ┴
		popn		%r1
		ld.w		%ahr, %r1
		ld.w		%alr, %r0
		popn		%r15
		reti				;// これで割り込み許可になる。
");

/*****************************************************************************
 *	グローバル関数
 *****************************************************************************/

/*static*/ void _idle_ret(int exit_code) __attribute__((noreturn));
           void idle_exit(int exit_code) __attribute__((noreturn));
asm("
		.code
		.align		1
		.global		idle_exit
_idle_ret:
		ld.w		%r12, %r10		;// %r12 := exit_code
idle_exit:
		;// idle_yield()から呼ばれた場合、これ以降プリエンプトが発生しないよう、割り込み禁止。
		ld.w		%psr, %r8		;// %psr[IE] := 0
		;// アイドルスレッドの終了コードを格納し、アイドルスレッドのコンテキストを破棄します。
		xld.w		[idle_exit_code], %r12	;// idle_exit_code := exit_code
		xld.w		[idle_sp], %r8		;// idle_sp        := NULL
		;// メインスレッドのコンテキストを復元します。
		xld.w		%r0, [main_sp]		;// %r0 := main_sp
		ld.w		%sp, %r0		;// %sp := main_sp
		;// [%sp+ 0] := %alr ┬ pceTimerSetContextSwitcher()「スタックフレームの構\成」仕様
		;// [%sp+ 4] := %ahr │
		;// [%sp+ 8] := %r0  │
		;// [%sp+12] := %r1  │
		;// [%sp+16] := %r2  │
		;// [%sp+20] := %r3  │
		;// [%sp+24] := %r4  │
		;// [%sp+28] := %r5  │
		;// [%sp+32] := %r6  │
		;// [%sp+36] := %r7  │
		;// [%sp+40] := %r8  │
		;// [%sp+44] := %r9  │
		;// [%sp+48] := %r10 │
		;// [%sp+52] := %r11 │
		;// [%sp+56] := %r12 │
		;// [%sp+60] := %r13 │
		;// [%sp+64] := %r14 │
		;// [%sp+68] := %r15 │
		;// [%sp+72] := %psr │
		;// [%sp+76] := %pc  ┴
		popn		%r1
		ld.w		%ahr, %r1
		ld.w		%alr, %r0
		popn		%r15
		reti					;// idle_yield()から呼ばれた場合は、割り込み禁止のまま帰る。
							;// idle_wait() から呼ばれた場合は、元の割り込み状態に戻る。
");

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

//{{2009/10/12:idle_exit()追加とidle_stub()廃止に伴い変更
//void
//idle_init(int (*fn)(int param), int param)
//{
//	if(fn) {
//		/* アイドルスレッドのコンテキストを作成。 */
//		idle_sp = &idle_stack[IDLE_STACK_SIZE / sizeof(int) - 22];
//	//	idle_sp[ 0] = /*don't care*/;	// %alr := ?         ┬ pceTimerSetContextSwitcher()「スタックフレームの構\成」仕様
//	//	idle_sp[ 1] = /*don't care*/;	// %ahr := ?         │
//	//	idle_sp[ 2] = /*don't care*/;	// %r0  := ?         │
//	//	idle_sp[ 3] = /*don't care*/;	// %r1  := ?         │
//	//	idle_sp[ 4] = /*don't care*/;	// %r2  := ?         │
//	//	idle_sp[ 5] = /*don't care*/;	// %r3  := ?         │
//	//	idle_sp[ 6] = /*don't care*/;	// %r4  := ?         │
//	//	idle_sp[ 7] = /*don't care*/;	// %r5  := ?         │
//	//	idle_sp[ 8] = /*don't care*/;	// %r6  := ?         │
//	//	idle_sp[ 9] = /*don't care*/;	// %r7  := ?         │
//		idle_sp[10] = 0;		// %r8  := bp        │
//	//	idle_sp[11] = /*don't care*/;	// %r9  := ?         │
//	//	idle_sp[12] = /*don't care*/;	// %r10 := ?         │
//	//	idle_sp[13] = /*don't care*/;	// %r11 := ?         │
//	//	idle_sp[14] = /*don't care*/;	// %r12 := param     │
//	//	idle_sp[15] = /*don't care*/;	// %r13 := ?         │
//	//	idle_sp[16] = /*don't care*/;	// %r14 := ?         │
//	//	idle_sp[17] = /*don't care*/;	// %r15 := ?         │
//		idle_sp[18] = 0x00000010;	// %psr := IL=0,IE=1 │
//		idle_sp[19] = (int)idle_stub;	// %pc  := idle_stub ┴
//		idle_sp[20] = (int)fn;		// idle_stub()で使用
//		idle_sp[21] = param;		// idle_stub()で使用
//	} else {
//		/* アイドルスレッドのコンテキストを破棄。 */
//		idle_sp = NULL;
//	}
//}
//}}2009/10/12:idle_exit()追加とidle_stub()削除に伴い変更
//↓ * Mon Oct 12 05:55:33 JST 2009 Naoyuki Sawa
//↓ - アイドル関数の起動のためにidle_stub()を経由する必要が無くなり、初期スタックの構\成が変りました。
void
idle_init(int (*fn)(int param), int param)
{
	if(fn) {
		/* アイドルスレッドのコンテキストを作成。 */
		idle_sp = &idle_stack[IDLE_STACK_SIZE / sizeof(int) - 22];
	//	idle_sp[ 0] = /*don't care*/;	// %alr  := ?         ┬ pceTimerSetContextSwitcher()「スタックフレームの構\成」仕様
	//	idle_sp[ 1] = /*don't care*/;	// %ahr  := ?         │
	//	idle_sp[ 2] = /*don't care*/;	// %r0   := ?         │
	//	idle_sp[ 3] = /*don't care*/;	// %r1   := ?         │
	//	idle_sp[ 4] = /*don't care*/;	// %r2   := ?         │
	//	idle_sp[ 5] = /*don't care*/;	// %r3   := ?         │
	//	idle_sp[ 6] = /*don't care*/;	// %r4   := ?         │
	//	idle_sp[ 7] = /*don't care*/;	// %r5   := ?         │
	//	idle_sp[ 8] = /*don't care*/;	// %r6   := ?         │
	//	idle_sp[ 9] = /*don't care*/;	// %r7   := ?         │
		idle_sp[10] = 0;		// %r8   := bp        │
	//	idle_sp[11] = /*don't care*/;	// %r9   := ?         │
	//	idle_sp[12] = /*don't care*/;	// %r10  := ?         │
	//	idle_sp[13] = /*don't care*/;	// %r11  := ?         │
		idle_sp[14] = param;		// %r12  := param     │
	//	idle_sp[15] = /*don't care*/;	// %r13  := ?         │
	//	idle_sp[16] = /*don't care*/;	// %r14  := ?         │
	//	idle_sp[17] = /*don't care*/;	// %r15  := ?         │
		idle_sp[18] = 0x00000010;	// %psr  := IL=0,IE=1 │
		idle_sp[19] = (int)fn;		// %pc   := fn        ┴
		idle_sp[20] = (int)_idle_ret;	// %retp := _idle_ret
	} else {
		/* アイドルスレッドのコンテキストを破棄。 */
		idle_sp = NULL;
	}
}

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

//{{2009/06/21:残りタイムスライスの算出にapptm_wkを参照するよう変更
//void
//idle_yield()
//{
//	static int t0;								/* ユーザー処理の開始時刻 */
//	       int t1 = pceTimerGetCount();					/* ユーザー処理の終了時刻を取得 */
//	       int dt = t1 - t0;						/* ユーザー処理の経過時間 */
//	   idle_count = pceAppSetProcPeriod(INVALIDVAL) - dt;			/* 残りタイムスライス */
//	if(idle_count > 0) {							/* タイムスライスが残っていたら */
//		if(idle_sp) {							/* アイドルスレッドが未完了なら */
//ENTER_CS;
//			pceTimerSetContextSwitcher(idle_context_switcher);	/* プリエンプトを設定 */
//			idle_exec();						/* アイドルスレッドに切り替える */
//			pceTimerSetContextSwitcher(NULL);			/* プリエンプトを解除 */
//LEAVE_CS;
//		}
//	}
//	yield();								/* システム処理を実行 */
//	t0 = pceTimerGetCount();						/* ユーザー処理の開始時刻を格納 */
//}
//}}2009/06/21:残りタイムスライスの算出にapptm_wkを参照するよう変更
//↓ * Sun Jun 21 21:42:18 JST 2009 Naoyuki Sawa
//↓ - clippce.cにapptm_wk変数を追加したことに伴い、残りタイムスライスの算出にapptm_wkを参照するよう変更しました。
void
idle_yield()
{
	int dt = pceTimerGetCount() - apptm_wk;					/* ユーザー処理の経過時間 */
	idle_count = pceAppSetProcPeriod(INVALIDVAL) - dt;			/* 残りタイムスライス */
	if(idle_count > 0) {							/* タイムスライスが残っていたら */
		if(idle_sp) {							/* アイドルスレッドが未完了なら */
ENTER_CS;
			pceTimerSetContextSwitcher(idle_context_switcher);	/* プリエンプトを設定 */
			idle_exec();						/* アイドルスレッドに切り替える */
			pceTimerSetContextSwitcher(NULL);			/* プリエンプトを解除 */
LEAVE_CS;
		}
	}
	yield();								/* システム処理を実行 */
}

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

int
idle_wait()
{
	if(idle_sp) {		/* アイドルスレッドが未完了なら */
		idle_exec();	/* プリエンプトを設定せずに、アイドルスレッドに切り替える */
	}
	return idle_exit_code;	/* アイドルスレッドの終了コード */
}

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

int
idle_stat()
{
	return (int)idle_sp;
}

