/*	
 *	cliptask.c
 *
 *	タスクスケジューラ (コンテキストスイッチ版)
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2006 Naoyuki Sawa
 *
 *	* Fri Jun 09 22:27:00 JST 2006 Naoyuki Sawa
 *	- 1st リリース。
 *	  旧いcliptask.cは、keepフォルダに保存しました。
 *	* Wed Jun 14 00:44:00 JST 2006 Naoyuki Sawa
 *	- task_kill()を追加しました。
 */
#include "clip.h"

/****************************************************************************
 *	説明
 ****************************************************************************/

#if 0

●タスク状態について

・実行可能\かつ実行中であるタスクを、『実行中タスク』(running_task)と呼ぶことにします。

・実行可能\であるタスクを、『実行可能\タスク』(ready_task)と呼ぶことにします。
　『実行中タスク』も、『実行可能\タスク』の一種と見なすことにします。

・事象を待って停止しているタスクを、『封鎖タスク』(blocked_task)と呼ぶことにします。

・終了したがリソ\ース未回収であるタスクを、『終了タスク』(stopped_task)と呼ぶことにします。
　(Unixプロセスの、『ゾンビプロセス』の状態に相当します。)
　タスクの終了を待ってリソ\ースを回収するには、task_wait()を使います。
　タスクの終了を待たずにリソ\ースを回収するには、task_kill()を使います。

●イベント状態について

・イベントがセットされている状態を、『シグナル状態』(signaled)と呼ぶことにします。

・イベントがリセットされている状態を、『非シグナル状態』(not_signaled)と呼ぶことにします。

●イベント待ちについて

・『非シグナル状態』である、イベントAが存在します。
　イベントAが『シグナル状態』になるのを待って停止している、タスクBが存在します。(『封鎖タスク』)
　このとき、イベントAを、『待たれるイベント』と呼ぶことにします。

●タスク終了待ちについて

・終了していない、タスクAが存在します。(『実行中タスク』『実行可能\タスク』『封鎖タスク』)
　タスクAが終了するのを待って停止している、タスクBが存在します。(『封鎖タスク』)
　このとき、タスクAを、『待たれるタスク』と呼ぶことにします。

●タスク強制終了について

・終了していない、または、『終了タスク』である、タスクAが存在します。
　タスクBがtask_kill()を呼び出して、直ちにタスクAのリソ\ースを回収することができます。
　このとき、タスクAを、『強制終了されるタスク』と呼ぶことにします。

・終了していない、タスクAが存在します。(『実行中タスク』『実行可能\タスク』『封鎖タスク』)
　タスクAが終了するのを待って停止している、タスクBが存在します。(『封鎖タスク』)
　このとき、タスクAを、『待たれるタスク』と呼ぶことにします。

●タスク状態と、登録について

・『実行可能\タスク』は、『実行可能\タスクリスト』(ready_task_list)に登録されています。

・『実行中タスク』は、『実行中タスク』(running_task)に登録されています。
　『実行中タスク』は『実行可能\タスク』でもあるので、『実行可能\タスクリスト』(ready_task_list)にも登録されています。

・イベント待ちである『封鎖タスク』は、待たれるイベントの『封鎖タスクリスト』(blocked_task_list)に登録されています。
　イベントの『封鎖タスクリスト』には、空か、または、そのイベントを待っている複数のタスクが登録されています。

・タスク終了待ちである『封鎖タスク』は、待たれるタスクの『封鎖タスクリスト』(blocked_task_list)に登録されています。
　タスクの『封鎖タスクリスト』は、空か、または、そのタスクの終了を待っている唯一のタスクが登録されています。

・『終了タスク』は、どのタスクリストにも登録されていません。
　『終了タスク』であることを示すために、明示的に「TASK.task_list_entry.Flink=NULL(=Blink=NULL)」としておきます。
　『終了タスク』は、他のタスクによってポインタが保持されているはずです。(さもなくば、リソ\ースリークとなります。)
　他のタスクが、task_wait()、または、task_kill()を呼び出すことによって、『終了タスク』のリソ\ースが回収されます。

#endif

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

#define DEFAULT_STACK_SIZE	0x1000	/* 既定のスタックサイズ (調整可) */

static LIST_ENTRY ready_task_list;	/* 実行可能\タスクリスト */
static TASK* running_task;		/* 実行中タスク */

static void task_select() __attribute__((unused));
/*static*/ void task_stub() __attribute__((noreturn));	/* 警告抑制のためstatic省略 */
/*static*/ void task_schedule();			/* 警告抑制のためstatic省略 */
static void event_set_pulse(EVENT* event, int pulse);

/****************************************************************************
 *	タスク
 ****************************************************************************/

void
task_init()
{
	TASK* task;

	/* 実行可能\タスクリストを初期化します。 */
	InitializeListHead(&ready_task_list);

	/* この関数を呼び出したコンテキストを、システムアイドルタスクとして登録します。
	 * - running_task->spは、初回タスクスイッチ時に格納されるので、初期化不要です。
	 */
	task = calloc(sizeof(TASK), 1);
	if(!task) {
		DIE();
	}
	task->priority = INT_MIN; /* アイドル優先度 */
	InsertTailList(&ready_task_list, &task->task_list_entry);

	/* この関数を呼び出したコンテキストが、初期化直後の『実行中タスク』となります。 */
	running_task = task;
}

TASK*
task_get_current()
{
	/* 実行中タスクを返します。
	 * - 実行中タスクがNULLになることは、有り得ません。
	 *   もしもここでアサートした場合は、おそらくtask_init()の呼び忘れです。
	 */
	if(!running_task) {
		DIE();
	}
	return running_task;
}

TASK*
task_create(int priority, int (*task_proc)(void* param), void* param, int stack_size)
{
	TASK* task;

	/* スタックサイズを決定します。 */
	if(!stack_size) {
		stack_size = DEFAULT_STACK_SIZE;
	}
	stack_size = (stack_size + 3) & ~3; /* スタックはWord単位 */

	/* タスク構\造体とスタックのメモリを確保します。
	 *
	 *	+--------------+ task
	 *	|              |
	 *	| sizeof(TASK) |
	 *	|              |
	 *	+--------------+
	 *	|              |
	 *	|  stack_size  |
	 *	|              |
	 *	+--------------+ task->sp
	 */
	task = calloc(sizeof(TASK) + stack_size, 1);
	if(!task) {
		DIE();
	}
	task->sp = (int*)((int)task + sizeof(TASK) + stack_size);

	/* 作成したタスクの、スタックを初期化します。 */
	/*{{task_stub*/
	*--task->sp = (int)param;	/* popn %r1 -> %r1 */
	*--task->sp = (int)task_proc;	/*          -> %r0 */
	/*}}task_stub*/
	/*{{task_schedule*/
	*--task->sp = (int)task_stub;	/* ret      -> %pc */
	*--task->sp = 0;		/* popn %r3 -> %r3 */
	*--task->sp = 0;		/*          -> %r2 */
	*--task->sp = 0;		/*          -> %r1 */
	*--task->sp = 0;		/*          -> %r0 */
	/*}}task_schedule*/

	/* 作成したタスクに、実行優先度を格納します。 */
	task->priority = priority;

	/* 作成したタスクの、封鎖タスクリストを初期化します。 */
	InitializeListHead(&task->blocked_task_list);

	/* 作成したタスクを、実行可能\タスクリストに登録します。 */
	InsertTailList(&ready_task_list, &task->task_list_entry);

	/* タスクスケジューリングを実行します。 */
	task_schedule();

	return task;
}

void
task_exit(int exit_code) /*__attribute__((noreturn))*/
{
	TASK* current_task = task_get_current();

	/* 終了するタスクに、終了コードを格納します。 */
	current_task->exit_code = exit_code;

	/* 終了するタスクを、実行可能\タスクリストから登録解除します。 */
	RemoveEntryList(&current_task->task_list_entry);
	current_task->task_list_entry.Flink = NULL; /* 『終了タスク』であることを示します */
	current_task->task_list_entry.Blink = NULL; /* 一応、こちらもクリア(必須ではない) */

	/* 終了するタスクに、封鎖タスクが登録済みならば... */
	if(!IsListEmpty(&current_task->blocked_task_list)) {
		/* 封鎖タスクを登録解除し、実行可能\タスクリストに登録します。(起床) */
		InsertTailList(&ready_task_list, RemoveHeadList(&current_task->blocked_task_list));
	}

	/* タスクスケジューリングを実行します。 */
	task_schedule();

	/* 終了したタスクが、実行可能\タスクリストに再登録されることは有りません。
	 * すなわち、上のタスクスケジューリングが処理を返すことは、有り得ません。
	 */
	DIE();
}

int
task_wait(TASK* task)
{
	TASK* current_task = task_get_current();
	//
	int exit_code;

	/* 自分自身の終了を待つことはできません。 */
	if(task == current_task) {
		DIE();
	}

	/* 一つのタスクの終了を、複数のタスクが待つことはできません。 */
	if(!IsListEmpty(&task->blocked_task_list)) {
		DIE();
	}

	/* 『待たれるタスク』が、まだ終了していなければ... */
	if(task->task_list_entry.Flink) {
		/* 現在のタスクを、実行可能\タスクリストから登録解除します。 */
		RemoveEntryList(&current_task->task_list_entry);

		/* 現在のタスクを、『待たれるタスク』の封鎖タスクリストに登録します。 */
		InsertTailList(&task->blocked_task_list, &current_task->task_list_entry);

		/* タスクスケジューリングを実行します。 */
		task_schedule();
	}

	/* ここへ来た時点で、『待たれるタスク』は終了しています。以下、完了処理です。 */

	/* 『待たれるタスク』の、終了コードを取り出します。 */
	exit_code = task->exit_code;

	/* 『待たれるタスク』の、タスク構\造体とスタックのメモリを開放します。(リソ\ース回収) */
	free(task);

	/* 『待たれるタスク』の、終了コードを返します。 */
	return exit_code;
}

void
task_kill(TASK* task)
{
	TASK* current_task = task_get_current();

	/* 自分自身を強制終了することはできません。 */
	if(task == current_task) {
		DIE();
	}

	/* タスク終了待ちの『封鎖タスク』が登録されている場合、タスクを強制終了することはできません。 */
	if(!IsListEmpty(&task->blocked_task_list)) {
		DIE();
	}

	/* 『強制終了されるタスク』が、まだ終了していなければ... */
	if(task->task_list_entry.Flink) {
		/* 『強制終了されるタスク』を、タスクリストから登録解除します。
		 * * まだ終了していないタスクは、次のいずれかのタスクリストに登録されているはずです。
		 *   - 実行可能\である場合、『実行可能\タスクリスト』(ready_task_list)
		 *   - タスク終了待ちである場合、タスクの『封鎖タスクリスト』(TASK.blocked_task_list)
		 *   - イベント待ちである場合、イベントの『封鎖タスクリスト』(EVENT.blocked_task_list)
		 *   どのタスクリストに登録されていても、同じ方法で登録解除できます。
		 */
		RemoveEntryList(&task->task_list_entry);
		//task->task_list_entry.Flink = NULL;
		//task->task_list_entry.Blink = NULL;
		//→すぐ下でリソ\ース回収を行うので、『終了タスク』であることを示す必要はありません。
	}

	/* 『強制終了されるタスク』の、タスク構\造体とスタックのメモリを開放します。(リソ\ース回収) */
	free(task);
}

void
task_suspend(TASK* task)
{
	/* サスペンド回数INT_MAXを上回って、サスペンド回数を増やすことはできません。 */
	if((task->suspend < 0) || (task->suspend > INT_MAX - 1)) {
		DIE();
	}

	/* サスペンド回数を増やします。 */
	task->suspend++;

	/* タスクスケジューリングを実行します。 */
	task_schedule();
}

void
task_resume(TASK* task)
{
	/* サスペンド回数0を下回って、サスペンド回数を減らすことはできません。 */
	if((task->suspend < 0 + 1) || (task->suspend > INT_MAX)) {
		DIE();
	}

	/* サスペンド回数を減らします。 */
	task->suspend--;

	/* タスクスケジューリングを実行します。 */
	task_schedule();
}

/* 次に実行するタスクを選択し、『実行中タスク』(running_task)に格納します。
 * [note]
 *	* 『実行可能\タスクリスト』(ready_task_list)を検索し、次に実行するタスクを決定します。
 *	   次に実行するタスクは、実行可能\なタスクのうち、最も高い実行優先度を持つタスクです。
 */
static void
task_select() /*__attribute__((unused))*/
{
	LIST_ENTRY* list_head = &ready_task_list;
	LIST_ENTRY* list_entry = list_head->Flink;
	TASK* next_task = NULL;

	/* 実行可能\なタスクのうち、最も高い実行優先度を持つタスクを検索します。 */
	while(list_entry != list_head) {
		TASK* task = CONTAINING_RECORD(list_entry, TASK, task_list_entry);
		if(!task->suspend) {
			if(!next_task ||
			   (task->priority > next_task->priority)) {
				next_task = task;
			}
		}
		list_entry = list_entry->Flink;
	}

	/* 次に実行するタスクを、『実行中タスク』(running_task)に格納します。
	 * * 実行可能\なタスクが一つも無かった場合は、アサートします。
	 *   もし、ここでアサートした場合は、以下のような原因が考えられます。
	 *   - 誤ってシステムアイドルタスクを終了した。システムアイドルタスクを終了してはいけない。
	 *   - システムアイドルタスクがサスペンドされており、かつ、サスペンドを解除する要因が無い。
	 *   - システムアイドルタスクが事象待ちで封鎖しており、かつ、デッドロック状態となっている。
	 *   いずれの原因も、アプリケーションプログラムのバグです。
	 */
	if(!next_task) {
		DIE();
	}
	running_task = next_task;
}

/* タスク関数の呼び出しと、暗黙のtask_exit()呼び出しを行います。
 */
/* static void task_stub() __attribute__((noreturn)) */
asm("
	.code
	.align	1
task_stub:
	; この関数は処理を返さないので、%r0と%r1を破壊しても大丈夫です。
	popn	%r1		; %r0 = task_proc, %r1 = param
	call.d	%r0		; exit_code = task_proc(param)
	ld.w	%r12, %r1	; *delay*
	xjp.d	task_exit	; task_exit(exit_code)
	ld.w	%r12, %r10	; *delay*
");

/* タスクスケジューリングを実行します。
 * [note]
 *	* この関数は、以下の処理を行います。
 *		1. 現在の『実行中タスク』のコンテキストを、running_task->spに保存します。
 *		2. task_select()を呼び出し、次に実行するタスクをrunning_taskに取得します。
 *		3. 新しい『実行中タスク』のコンテキストを、running_task->spから復元します。
 */
/* static void task_schedule() */
asm("
	.code
	.align	1
task_schedule:
	pushn	%r3
	xld.w	%r4, [running_task]	; running_task->sp = %sp
	ld.w	%r5, %sp
	xld.w	[%r4+0], %r5
	;
	xcall	task_select
	;
	xld.w	%r4, [running_task]	; %sp = running_task->sp
	xld.w	%r5, [%r4+0]
	ld.w	%sp, %r5
	popn	%r3
	ret
");

/****************************************************************************
 *	イベント
 ****************************************************************************/

EVENT*
event_create(int manual_reset)
{
	EVENT* event;

	/* イベント構\造体のメモリを確保します。 */
	event = calloc(sizeof(EVENT), 1);
	if(!event) {
		DIE();
	}

	/* 作成したイベントに、リセット区分を格納します。 */
	event->manual_reset = manual_reset;

	/* 作成したイベントの、封鎖タスクリストを初期化します。 */
	InitializeListHead(&event->blocked_task_list);

	return event;
}

void
event_delete(EVENT* event)
{
	/* イベント待ちの『封鎖タスク』が残っている場合、イベントを削除することはできません。 */
	if(!IsListEmpty(&event->blocked_task_list)) {
		DIE();
	}

	/* イベント構\造体のメモリを開放します。 */
	free(event);
}

void
event_wait(EVENT* event)
{
	TASK* current_task = task_get_current();

	/* イベント状態が『シグナル状態』ならば... */
	if(event->signal) {
		/* 自動リセットイベントならば、『非シグナル状態』に戻します。 */
		if(!event->manual_reset) {
			event->signal = 0;
		}

	/* イベント状態が『非シグナル状態』ならば... */
	} else {
		/* 現在のタスクを、実行可能\タスクリストから登録解除します。 */
		RemoveEntryList(&current_task->task_list_entry);

		/* 現在のタスクを、『待たれるイベント』の封鎖タスクリストに登録します。 */
		InsertTailList(&event->blocked_task_list, &current_task->task_list_entry);
	}

	/* タスクスケジューリングを実行します。 */
	task_schedule();
}

void
event_set(EVENT* event)
{
	event_set_pulse(event, 0/*セット*/);
}

void
event_pulse(EVENT* event)
{
	event_set_pulse(event, 1/*パルス*/);
}

void
event_reset(EVENT* event)
{
	/* イベント状態を『非シグナル状態』に変更します。 */
	event->signal = 0;

	/* イベントリセットは、タスク切り替えの要因となり得ないので、タスクスケジューリングは不要です。 */
}

/* イベントを発行します。(セット、または、パルス)
 * [in]
 *	event		発行するイベント。
 * [note]
 *	* event_set()、event_pulse()から利用します。
 */
static void
event_set_pulse(EVENT* event, int pulse)
{
	/* イベント状態を『シグナル状態』に変更します。 */
	event->signal = 1;

	/* 手動リセットイベントならば、封鎖タスクをすべて起床し、イベント状態を『シグナル状態』のままにします。
	 * 自動リセットイベントならば、封鎖タスクを一つだけ起床し、イベント状態を『非シグナル状態』に戻します。
	 * 自動リセットイベントであっても、封鎖タスクが無ければ、イベント状態を『シグナル状態』のままにします。
	 */
	while(!IsListEmpty(&event->blocked_task_list)) {
		/* 封鎖タスクを登録解除し、実行可能\タスクリストに登録します。(起床) */
		InsertTailList(&ready_task_list, RemoveHeadList(&event->blocked_task_list));

		/* 自動リセットイベントならば、イベント状態を『非シグナル状態』に戻して、抜けます。 */
		if(!event->manual_reset) {
			event->signal = 0;
			break;
		}
	}

	/* event_pulse()ならば、手動リセットイベントであっても、イベント状態を『非シグナル状態』に戻します。 */
	if(pulse) {
		event->signal = 0;
	}

	/* タスクスケジューリングを実行します。 */
	task_schedule();
}

