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

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

/* タスク構造体  */
typedef struct _TASK {
	/*{{レイアウト固定*/
	int* sp;			/* %sp保存 */
	/*}}レイアウト固定*/
	int suspend;			/* サスペンド回数 (1以上 = 停止, 0 = 動作, 0未満 = 設定不可) */
	int priority;			/* 実行優先度 (INT_MAX = 最高 〜 0 = 通常 〜 INT_MIN = 最低) */
	int exit_code;			/* 終了コード */
	LIST_ENTRY blocked_task_list;	/* 封鎖タスクリスト                                                           */
					/* * リストは空か、または、唯一の終了待ちタスクが登録されています。           */
					/*   二つ以上の終了待ちタスクが登録されていることは、有り得ません。           */
					/*   - このタスクは終了しておらず、終了待ちタスクは無い → 空リスト           */
					/*   - このタスクは終了しておらず、終了待ちタスクは有る → 終了待ちタスク登録 */
					/*   - このタスクは終了済みであり、終了待ちタスクは無い → 空リスト           */
					/*   - このタスクは終了済みであり、終了待ちタスクは有る → 終了待ちタスク登録 */
	LIST_ENTRY task_list_entry;	/* 各種タスクリストへの登録用                                                          */
					/* * このタスクが『終了タスク』である場合を除き、必ずタスクリストに登録されています。  */
					/*   すなわち、task_list_entry.Flink!=NULLであり、後述の『終了タスク』と区別できます。 */
					/*   登録先のタスクリストは、以下の通りです。                                          */
					/*   - 実行可能である場合、『実行可能タスクリスト』(ready_task_list)                   */
					/*   - タスク終了待ちである場合、タスクの『封鎖タスクリスト』(TASK.blocked_task_list)  */
					/*   - イベント待ちである場合、イベントの『封鎖タスクリスト』(EVENT.blocked_task_list) */
					/* * このタスクが『終了タスク』(終了済み、かつ、終了待ちタスクが未登録)である場合、    */
					/*   明示的にtask_list_entry.Flink=NULLとして、『終了タスク』であることを示します。    */
} TASK;

/* タスクスケジューラを初期化します。
 * [note]
 *	* タスク機能を利用するアプリケーションが、最初に一度だけ呼び出してください。
 *	  この関数を呼び出さずにタスク機能を利用しようとすると、異常動作となります。
 *
 *		int main() {
 *			...
 *			task_init(); // タスク機能を利用するならば、必要!!
 *			...
 *		}
 *
 *	* task_init()と対になる、タスク機能のクリーンアップ関数はありません。
 *	  task_init()を呼び出したアプリケーションが、終了前に呼び出す必要のある関数はありません。
 *	  また、いくつかのタスクが残ったままで、アプリケーションを終了しても構いません。
 *
 *	* この関数を呼び出したコンテキストを、システムアイドルタスクとして登録します。
 *	  システムアイドルタスクの実行優先度は、INT_MIN(アイドル優先度)に固定で、変更できません。
 *
 *	* システムアイドルタスクは、終了してはいけません。(task_exit()呼び出し不可)
 *	  誤ってシステムアイドルタスクを終了してしまった場合の動作は、未定義です。
 *
 *		int main() {
 *			...
 *			task_init();
 *			...
 *			task_exit(0); // 誤り!!
 *		}
 */
void task_init();

/* 実行中タスクを取得します。
 * [out]
 *	戻り値		実行中タスク。
 * [note]
 *	* task_init()を呼び出した後は、常に何らかのタスクが実行中です。
 *	  task_get_current()がNULLを返すことは、絶対にありません。
 */
TASK* task_get_current();

/* タスクを作成します。
 * [in]
 *	priority	実行優先度。
 *	task_proc	アプリケーション定義のタスク関数。
 *	param		タスク関数に引き渡す、アプリケーション定義のパラメータ。
 *	stack_size	スタックサイズ。(バイト数)
 *			0を指定すると、既定のスタックサイズが採用されます。
 * [out]
 *	戻り値		作成したタスク。
 * [note]
 *	* タスクの実行優先度は、タスク作成時に指定し、以後、変更することはできません。
 *	  タスクの実行優先度を変更する関数を用意することは簡単ですが、実際のアプリケーションにおいて、
 *	  タスク作成後に実行優先度の変更が必要となる場面はまず無いと考えて、あえて用意しませんでした。
 *
 *	* タスク作成直後の、サスペンド回数の初期値は0です。(=サスペンドしていない)
 *	  すなわち、task_create()を呼び出したタスクよりも、作成されたタスクの実行優先度が高い場合、
 *	  task_create()が処理を返すのは、作成されたタスクが封鎖されるか、または、終了後となります。
 *
 *		int main() {
 *			...
 *			task_init();                                 // 実行優先度=INT_MIN
 *			...
 *			task1 = task_create(1, task1_proc, NULL, 0); // 実行優先度=1
 *			// この時点で、task1は封鎖されているか、既に終了している。
 *			...
 *		}
 *
 *		int task1_proc(void* param) {                        // 実行優先度=1
 *			...
 *			task2 = task_create(0, task2_proc, NULL, 0); // 実行優先度=0
 *			// この時点で、task2の処理は、まだ実行されていない。
 *			...
 *		}
 *
 *	* 初期状態でサスペンドしているタスクを作成する方法は、用意していません。
 *	  もし必要ならば、タスク関数の先頭に自分自身をサスペンドする処理を配置することで実現できます。
 *
 *		int task_proc(void* param) {
 *			task_suspend(task_get_current());
 *			...
 *		}
 */
TASK* task_create(int priority, int (*task_proc)(void* param), void* param, int stack_size);

/* タスクを終了します。
 * [in]
 *	exit_code	終了コード。
 * [note]
 *	* この関数は、処理を返しません。
 *
 *	* アプリケーション定義のタスク関数は、以下の二種類の方法でタスクを終了できます。
 *
 *		1. 終了コードを指定して、task_exit()を呼び出す。
 *
 *			int task_proc(void* param) {
 *				...
 *				task_exit(result);
 *			}
 *
 *		2. 終了コードを伴って、タスク関数からreturnする。
 *
 *			int task_proc(void* param) {
 *				...
 *				return result;
 *			}
 */
void task_exit(int exit_code) __attribute__((noreturn));

/* タスクの終了を待って、リソースを回収します。
 * [in]
 *	task		待たれるタスク。
 * [out]
 *	戻り値		終了コード。
 * [note]
 *	* 自分自身の終了を待つことはできません。
 *	  自分自身の終了を待とうとすると、アサートします。
 *
 *		reulst = task_wait(task_get_current()); // 誤り!!
 *
 *	* task_wait()を呼び出すことで、終了したタスクのリソースが回収されます。
 *	  task_wait()を呼び出さないと、終了したタスクのリソースが回収されずに、残ってしまいます。
 *	  繰り返し作成するタスクに対しては、task_wait()を呼び出して、リソースを回収してください。
 *
 *		for(;;) {
 *			...
 *			task = task_create(0, task_proc, NULL, 0);
 *			...
 *			result = task_wait(task); // 必要!!
 *			...
 *		}
 *
 *	* 一つのタスクの終了を、複数のタスクが待つことはできません。
 *	  一つのタスクの終了を、複数のタスクが待とうとすると、アサートします。
 *
 *		TASK* task4;
 *
 *		int task1_proc(void* param) {
 *			...
 *			task4 = task_create(0, task4_proc, NULL, 0);
 *			...
 * 		}
 *
 *		int task2_proc(void* param) {
 *			...
 *			result = task_wait(task4); // どちらか一方が誤り!!
 *			...
 * 		}
 *
 *		int task3_proc(void* param) {
 *			...
 *			result = task_wait(task4); // どちらか一方が誤り!!
 *			...
 * 		}
 */
int task_wait(TASK* task);

/* タスクの終了を待たずに、リソースを回収します。
 * [in]
 *	task		強制終了されるタスク。
 * [note]
 *	* 自分自身を強制終了することはできません。
 *	  自分自身を強制終了しようとすると、アサートします。
 *
 *		task_kill(task_get_current()); // 誤り!!
 *
 *	* 既に終了しているタスクに対して、task_kill()を呼び出すこともできます。
 *	  強制終了されるタスクが、既に終了しているか否かによって、task_kill()の結果に違いはありません。
 *
 *	* タスク1がタスク2の終了を待っているときに、タスク3がタスク2を強制終了することはできません。
 *	  このような仕様とした理由は、タスク1が呼び出したtask_wait()の戻り値が定義できないためです。
 *
 *		int task1_proc(void* param) {
 *			...
 *			result = task_wait(task2);
 *			...
 *		}
 *
 *		int task3_proc(void* param) {
 *			...
 *			task_kill(task2); // 誤り!!
 *			...
 *		}
 *
 *	  この問題を避けるには、各タスク設計時に、task_wait()によってリソース回収されるべきタスクか、
 *	  または、task_kill()によってリソース回収されるべきタスクかを、明確にしておく必要があります。
 *
 *	* タスク1がタスク2の終了を待ってリソースを回収するために、task_wait()を呼び出して封鎖されているとき、
 *	  タスク3がタスク1を強制終了すると、タスク1はタスク2の封鎖タスクリストから登録解除されます。
 *	  アプリケーションが、このケースを意識して、別の手段によってタスク2のリソースを回収しない限り、
 *	  タスク2のリソースがリークしてしまいます。
 *
 *		int task1_proc(void* param) {
 *			...
 *			result = task_wait(task2); // この呼び出しが処理を返す前に...
 *			...
 *		}
 *
 *		int task3_proc(void* param) {
 *			...
 *			task_kill(task1);          // この呼び出しを行うと、task2がリソースリーク!!
 *			...
 *		}
 *
 *	  この問題は、タスクのリソース回収に限らず、task1がリソース回収処理を行うタスクである場合に、
 *	  その処理を行う前に強制終了されると、リソースがリークしてしまう問題であると言えます。
 *	  ライブラリ側で特に対策は設けていませんので、アプリケーション側にて注意してください。
 */
void task_kill(TASK* task);

/* タスクのサスペンド回数を増やします。
 * [in]
 *	task		サスペンド回数を増やすタスク。
 * [note]
 *	* サスペンド回数INT_MAXを上回って、サスペンド回数を増やすことはできません。
 */
void task_suspend(TASK* task);

/* タスクのサスペンド回数を減らします。
 * [in]
 *	task		サスペンド回数を減らすタスク。
 * [note]
 *	* サスペンド回数0を下回って、サスペンド回数を減らすことはできません。
 */
void task_resume(TASK* task);

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

/* イベント構造体  */
typedef struct _EVENT {
	int signal;			/* イベント状態 (0 = 非シグナル状態, 1 = シグナル状態) */
	int manual_reset;		/* リセット区分 (0 = 自動リセット, 1 = 手動リセット) */
	LIST_ENTRY blocked_task_list;	/* 封鎖タスクリスト */
} EVENT;

/* イベントを作成します。
 * [in]
 *	manual_reset	リセット区分。
 *			0を指定すると、自動リセットイベントを作成します。
 *			1を指定すると、手動リセットイベントを作成します。
 * [out]
 *	戻り値		作成したイベント。
 * [note]
 *	* イベントの初期状態は、『非シグナル状態』です。
 *
 *	* event_create()の引数で、イベントの初期状態を指定する方法は、あえて採りませんでした。
 *	  初期状態で『シグナル状態』のイベントが必要となるケースは、まず無いと考えたからです。
 *	  もし必要ならば、event_create()の直後に、event_set()を呼び出してください。
 *
 *		event = event_create(0または1); // 非シグナル状態
 *		event_set(event);               //   シグナル状態
 */
EVENT* event_create(int manual_reset);

/* イベントを削除します。
 * [in]
 *	event		削除するイベント。
 * [note]
 *	* イベント待ちの『封鎖タスク』が残っている場合、イベントを削除することはできません。
 *	  イベントを削除しようとすると、アサートします。
 *
 *		int task1_proc(void* param) {
 *			...
 *			event = event_create(0または1);
 *			task2 = task_create(0, task2_proc, NULL, 0);
 *			event_delete(event); // 誤り!!
 *			...
 *		}
 *
 *		int task2_proc(void* param) {
 *			...
 *			event_wait(event);
 *			...
 *		}
 */
void event_delete(EVENT* event);

/* イベントを待ちます。
 * [in]
 *	event		待たれるイベント。
 */
void event_wait(EVENT* event);

/* イベントを発行します。(セット)
 * [in]
 *	event		発行するイベント。
 * [note]
 *	* イベント状態を『シグナル状態』にします。
 *	  起床される『封鎖タスク』の数とイベント状態の遷移は、リセット区分と『封鎖タスク』の有無に依存します。
 *	  概ね、Windowsのイベント関数と同じ挙動です。Windowsのイベント関数仕様を参照してください。
 */
void event_set(EVENT* event);

/* イベントを発行します。(パルス)
 * [in]
 *	event		発行するイベント。
 * [note]
 *	* イベント状態を『シグナル状態』にし、直ちに『非シグナル状態』に戻します。
 *	  開放される『封鎖タスク』の数は、リセット区分に依存します。
 *	  概ね、Windowsのイベント関数と同じ挙動です。Windowsのイベント関数仕様を参照してください。
 */
void event_pulse(EVENT* event);

/* イベントを非シグナル状態にします。
 * [in]
 *	event		非シグナル状態にするイベント。
 */
void event_reset(EVENT* event);

#endif /*__CLIP_TASK_H__*/
