/*	"timerdrv.aska"
	OSASK/NEC98用のタイマールーチン(type-A ver.1.1)
	Copyright(C) 2002 川合秀実(川合堂)		*/

//	古いチップセットには対応していない。なぜなら、連続アクセスの際にウェイトが入っていないから。

#include "osaskinc.ask"

segment CODE(USE32, PARA);
default(code == CODE);

#define	DEBUG		1

struct SS0_struct {
	void [32];
	char ring0_count, [3];
	/* void * */ int [4], ring0call[6], [2];
	char cli_count, [3];
	/* void * */ int [4], cli_call[6], [2];
};

struct TimerNode {
	int signalbuf[32]; // タイマーシグナルは、普通よりも小さい
	int call_vector[8];
	int time[12], interval;
	// int near *back, near *next;
	int back, next;
	// これでちょうど64bytes.
};

struct TimerWork {
	int jmpvec; // 使っていない（timer type-Bのなごり）
	int longcounter; // 残り待ち時間（何回の割り込みを待つのか）
	int predict_PIT1_count /* タイムアウトしたときの値 */; // 使っていない
	char status, [3];

	// origin(0x0010)

	// 以下全て使っていない
	unsigned int PIT1_count0 /* 設定値 */;
	unsigned int PIT1_delta_100000000, PIT1_delta_10000, PIT1_delta_fffeffff /* それぞれ、どれだけ減るかを表したもの */;
	signed int PIT1_delta_max, PIT1_delta_min /* PIT1_count0 / 2, - PIT1_count0 / 2 */;
	int limit /* 先行実行限界値 */;

	void [20];
	void [192];

	// origin(0x0100)

	TimerNode head, last, timeout, freenode[3200 /* 50 * sizeof (TimerNode) */];
};

void far TimerSysCmd()
// FS:EBXからコマンド
{
	PUSH(DS);
	ESP -= 24;
	[SS:ESP +  0] = EAX;
	MOV(EAX, CS);
	[SS:ESP +  4] = ECX;
	EAX += 8;
	[SS:ESP +  8] = EDX;
	DS = AX;
	EAX = [FS:EBX]; // 4の倍数
	[SS:ESP + 12] = EBP;
	[SS:ESP + 16] = ESI;
	[SS:ESP + 20] = EDI;
	if (EAX != 4)
		asmout("JMP DWORD PTR DS:[TimerSC_JmpTbl+EAX]");
	asmout("JMP TimerSC_Special");
}

void far TimerInt()
{
	TimerWork *work == DS:0x0000;
	unsigned short PIC_mastar_OCW2 == 0x0000;

	PUSH(DS);
	PUSH(EAX);
	MOV(EAX, CS);
	EAX += 8;
	DS = AX;

	// 例外なく、割り込みはすぐに終了
	AL = 0x60 /* IRQ-00を終了 */;
	OUT(PIC_mastar_OCW2, AL);

	// 現在時刻の算出方法 : head.time - 残りカウント

	// 残りカウントの算出方法 : (longcounter - 1) * 0x10000 + PIT0のカウント
	// (大きな数や0のときは、IRQ0が来ているかどうかによって、longcounterを小さく見てやる必要があるかもしれない)
	// 0でIRQが来ていなかったら・・・そんなことはありえない。絶対に来ている。が、longcountは減らしてはいけない。

	// ただし、補正が必要。・・・補正というか、この時刻に対応するPIT1のカウントを算出しておく。



	// さらに高速化できる。・・・short_waitでも、わざとlongcounterを1にしておく。
	// 割り込みをキャンセルするルーチンは、statusを見る。0ならlong,1ならshort。
	// 2なら割り込み処理中。
}

/*	基本は32bitバージョン。まさに2^32カウントに一度、時刻補正割り込みが起きるから
	問題はない。ちなみに、この割り込みは１時間に１度である。もっというっと、
	時刻補正割り込みとは、何もしない割り込みのことである。・・・いや、違う、
	時刻補正割り込みは2^(32+16)カウントに一度である。これは、７年に一度。

	タイムアウトすると、すぐに現在時刻が更新され、インターバルも調節される。
	この状態になると、共通ルーチンに入る（割り込み、非割り込み）。
*/

/*	このバージョンでは、割り込みを禁止したまま次々と処理していくが、
	そうでないバージョンを作ることも可能だろう。しかし、どちらが好ましいかは分からない。
*/

void near TimerRegister();

void near TimerLongcount()
{
	TimerWork *work == DS:0x0000;

	work->longcounter--;
	/* (true) */ if (!= 0) {
		POP(EAX);
		POP(DS);
		IRETD();
	}
	// goto TimerCountout;
}

void near TimerCountout()
//	ここに来たら、CLI期間がかなり長くなるので、IRQ-00をmaskして
//	割り込み終了をやってしまう方がいいかもしれない。
//	しかし、ノードを繋ぎ変えているときに他の割り込みが発生し、
//	そいつがタイマーを設定しようとされると困る。

//	そのために、接続待機群をチェーンで繋いでおくか？
//	チェーンじゃなくて32エントリくらいのスタックがあればいいだろう。
//	この案は結構いいかも。

//	キーボードもシグナルを出すはめになるとわかったら、そうした方がいいだろう。

//	いや、やっぱりやらなくても平気かな？・・・うーん、悩ましい。

//	最終案：maskはしない。しなくていいように、最後の一発はmode0でやるようになっている。
//	TimerNodeを付け替えるときはIF = 0になっているが、それが済めばIF = 1になる。

//	割り込みのキャンセルができないため、逆に待ち時間1ですぐに確実に割り込ませる。
//	このために、タイムアウトチェックルーチンは、インラインで組み込む
{
	TimerWork *work == DS:0x0000;
	SS0_struct *ss0 == /* SS:(- sizeof (SS0_struct)) */ SS:0xffffffc0;

	asmout("#work_head EQU 256");
	asmout("#work_timeout EQU 384");

	asmout("#TN_call_vector EQU 32");
	asmout("#TN_back EQU 56");
	asmout("#TN_next EQU 60");

	PUSHAD();


//	タイムアウト群の存在をチェック。
//	work->head.intervalが正なら、EBXを加える
//	そのあとで、そのノードを切り離し、フライングタイムアウトルーチンへ
//	work->head.intervalまでもタイムアウトしてしまったら、これもフライングタイムアウトの末尾に加える

	TimerNode *tnode == DS:ESI, *tnext == DS:EDI, *tback == DS:EBX;

check_timeout:
	EAX = work->head.interval;
	EBX = 0;
	for (;;) {
		/* (false) */ if ((signed) EAX < 0) {
			// 割り込み予定時刻 - 現在時刻 = 残り時間
			(offset) tnode = work->head.next;
			EAX = tnode->time[0];
			ECX = tnode->time[4];
			EAX -= work->head.time[0];
			EDX = tnode->time[8];
			/* ECX -= work->head.time[4] + CF */ SBB(ECX, work->head.time[4]);
			/* EDX -= work->head.time[8] + CF */ SBB(EDX, work->head.time[8]);
		//	work->head.interval = EAX;
			ECX |= EDX;
			if (!= 0)
				goto to_invalid;
			if ((signed) EAX > 0)
				break;
	to_invalid:
		//	work->head.interval = 0x90000000;
			EAX = 0x90000000;
			break;
		}
		EAX -= EBX;
		asmout("JO #to_invalid");
	//	work->head.interval = EAX;
		if ((signed) EAX > 0)
			break;
		// タイムアウト
		(offset) tnode = work->head.next;
		(offset) tback = work->timeout.back;
		(offset) tnext = tnode->next;
		work->timeout.back = (offset) tnode;
		work->head.next = (offset) tnext;
		/* tnode->next = (offset) &(work->timeout) */ asmout("MOV DWORD PTR DS:[ESI+#TN_next],#work_timeout");
		tnode->back = (offset) tback;
		/* tnext->back = (offset) &(work->head) */ asmout("MOV DWORD PTR DS:[EDI+#TN_back],#work_head");
		tback->next = (offset) tnode;
		EBX = EAX;
		EAX = tnode->interval;
		EBX =- EBX;
	//	切り離して、タイムアウトチェーンに接続
	//	そして、負になったEAXを元に、EBX = - EAX;とし、
	//	EAXに新しいwork->head.intervalをロードして、ループに突入する。
	}

	(offset) tnode = work->timeout.next;
	work->head.interval = EAX;
	asmout("CMP ESI,#work_timeout");
	if (!=)
		goto exec3;

	if ((signed) EAX < 0)
		goto invalid_inverval;
		// この時、0x80000000以上の待ち時間があることを仮定していい・・・long32かmax_wait
		// max_waitから直接に32bit-intervalモードに移行することはない。かならず、long32を経由する
		// こうすることで、コードをコンパクトにできる
short_wait:

//	EAXだけ待つ。

	work->longcounter = EAX;
	ECX = 0;
//	TimerNode *tnode == DS:ESI;
	(offset) tnode = work->head.next;
	work->head.interval = ECX; // ECX == 0
	CMP(CL /* == 0 */, work->status);
	EDX = tnode->time[8];
	EAX = tnode->time[0];
	ECX = tnode->time[4];
	work->head.time[8] = EDX;
	work->head.time[0] = EAX;
	work->head.time[4] = ECX;
	if (!= 0)
		goto check_nesting;
	POPAD();
	POP(EAX);
	POP(DS);
	IRETD();

check_nesting:
	work->status = 0 /* 通常割り込み待機中(現在時刻は無効) */;

	ss0->cli_count--; // 割り込みルーチンの基本
	if (== 0) goto call_clicall;
	POPAD();
	POP(EAX);
	POP(DS);
	ss0->ring0_count--;
	if (== 0) goto call_ring0call;
	IRETD();

call_clicall:
//	PUSHAD();
	/* (ss0->clicall)() */ asmout("CALL FAR DWORD SS:[0FFFFFFC0H+038H]");
//	POPAD();
	POPAD();
	POP(EAX);
	POP(DS);
	ss0->ring0_count--;
	if (== 0) goto call_ring0call;
	IRETD();

call_ring0call:
	/* (ss0->ring0call)() */ jmp_system_count0();
//	IRETD();

exec3: // フライングタイムアウト用合流地点

	if (work->status == 0) {
		work->status = 1 /* タイムアウト処理中(現在時刻は有効) */;

		ss0->ring0_count++; // 割り込みルーチンの基本
		ss0->cli_count++; // (これをやったかどうかは、work->status != 0かどうかでわかる)
	}
	// 割り込み可能期間中は、タイマーチェーンを繋ぎ変えてよい。
	// 割り込みシグナルは、チェーンを切ってから、割り込みを許可して、
	// その後にコールされる。

	// タイムアウトチェーンから一つ取り出す。
//	(offset) tnode = work->timeout.next;
	(offset) tnext = tnode->next;
	/* (char) tnode->back = 1 */ asmout("MOV BYTE PTR DS:[ESI+#TN_back],1"); // このノードは時刻がセットされており、タイムアウトした(切り離された)
	work->timeout.next = (offset) tnext;
	/* tnext->back = (offset) &(work->timeout) */ asmout("MOV DWORD PTR DS:[EDI+#TN_back],#work_timeout");
//	STI(); // 2000/05/24 割り込み処理中は一貫してCLIしておくことになった。
	asmout("CALL FAR DWORD DS:[ESI+#TN_call_vector]");
//	CLI();
	if (CF == 1 /* 連続割込み要求 */) {
		// CFを1にするなら、ESIを壊してはいけない

		// ノードを更新して、登録・・・登録ルーチンは、
		// headの次のノードに繋ぐことがあったかどうかはチェックできるようにするが
		// 勝手にTimerExecuteを呼んだりはしない
		EAX += tnode->time[0];
		ADC(ECX, tnode->time[4]);
		tnode->time[0] = EAX;
		ADC(EDX, tnode->time[8]);
		tnode->time[4] = ECX;
		tnode->time[8] = EDX;
		TimerRegister(); // ESI, EDX:ECX:EAX に時刻。これが呼ばれるとき、現在時刻は有効。
	}

//	タイムアウト群の存在をチェック。
//	work->head.intervalが正なら、ESIを加える
//	そのあとで、そのノードを切り離し、フライングタイムアウトルーチンへ
//	work->head.intervalまでもタイムアウトしてしまったら、これもフライングタイムアウトの末尾に加える

	goto check_timeout;

invalid_inverval:
	// この時、0x80000000以上の待ち時間があることを仮定していい・・・long32かmax_wait
	(offset) tnode = work->head.next;
	EAX = tnode->time[0];
	ECX = tnode->time[4];
	EAX -= work->head.time[0];
	EDX = tnode->time[8];
	SBB(ECX, work->head.time[4]);
	SBB(EDX, work->head.time[8]);
	if (!= 0)
		goto long32_wait;
	if (ECX == 0)
		goto short_wait;

long32_wait:
	EAX -= 0xffffffff;
	SBB(ECX, 0);
	SBB(EDX, 0);
	EBX = 0x90000000;
	work->longcounter = 0xffffffff;
	if (== 0) {
		if (ECX == 0) {
			if ((signed) EAX > 0)
				EBX = EAX;
		}
	}
	EAX = 0;
	work->head.interval = EBX;
	work->head.time[0] += 0xffffffff;
	ADC(work->head.time[4], EAX);
	ADC(work->head.time[8], EAX);
	if (work->status != 0)
		goto check_nesting;
	POPAD();
	POP(EAX);
	POP(DS);
	IRETD();
}

void near GetTimeAndFixZero();
void near GetNowTime();

void near TimerRegister0()
// EDX:ECX:EAXのロードをやる
{
	TimerNode *tnode == DS:ESI;
	EAX = tnode->time[0];
	ECX = tnode->time[4];
	EDX = tnode->time[8];
}

void near TimerRegister()
// ESI, EDX:ECX:EAX に時刻。これが呼ばれるとき、現在時刻は有効。
// 割り込みを禁止しておくこと。
{
	asmout("#work_timeout EQU 384");
	asmout("#TN_next EQU 60");

	TimerWork *work == DS:0x0000;
	TimerNode *tnode == DS:ESI, *tnext == DS:EDI, *tback == DS:EBX;

	EAX -= work->head.time[0];
	SBB(ECX, work->head.time[4]);
	SBB(EDX, work->head.time[8]);
	/* (false) */ if ((unsigned) < 0 /* 既にタイムアウト? */) {
		// 正確には、0でもタイムアウトなのだが、EDXだけでは分からないので、放っておく。
		/* (false) */ if (work->status == 0) {
			EBX = ESI;
			GetTimeAndFixZero();
			ESI = EBX;
			TimerNode *tnode2 == DS:EBX;
			EAX = tnode2->time[0];
			ECX = tnode2->time[4];
			EAX -= work->head.time[0];
			EDX = tnode2->time[8];
			/* ECX -= work->head.time[4] + CF */ SBB(ECX, work->head.time[4]);
		//	work->status = 0;
			/* EDX -= work->head.time[8] + CF */ SBB(EDX, work->head.time[8]);
			if ((unsigned) >= 0 /* タイムアウトしてない */)
				goto entry2;
		}
		// タイムアウトに直接登録
		(offset) tback = work->timeout.back;
		work->timeout.back = (offset) tnode;
		/* tnode->next = (offset) &(work->timeout) */ asmout("MOV DWORD PTR DS:[ESI+#TN_next],#work_timeout");
		tnode->back = (offset) tback;
		tback->next = (offset) tnode;
		return;
	}
entry2:
	// ここに来るとき、EDXの状態によってZFがセットされている。しかも、EDXは負ではない。
	ECX |= EDX;
	if (!= 0)
		goto comp_time;
	if ((signed) EAX < 0)
		goto comp_time;

	// 残り時間は31bit以下。

	// CF == 0である限り、どんどん引く。
	// CF == 1になったら、invalid_intervalのせいか、単に時間が足りなくなったかのどちらかである。
	// どっちにしても、その前に挿入すべきなのは確実。

	ECX = work->head.interval; // これは負になりうるので、特別処理。
	/* (offset) tback = &work->head */ LEA((offset) tback, (int) work->head);
	EAX -= ECX;
	(offset) tnext = work->head.next;
	/* if (OF == 1) goto comp_time; */ asmout("JO #comp_time");
	if ((signed) < 0) // unsignedで比較すれば下のifは省略できるが、ECXが有効な負の値である場合を取り扱えない
		goto insert;
	if ((signed) ECX < 0xa0000000)
		goto insert;

	do {
		(offset) tback = (offset) tnext; // この文は実行速度を減少しない(AGI)。
		EAX -= tnext->interval;
		(offset) tnext = tnext->next;
	} while ((unsigned) >= 0);

	// time      00     02     07     11     99
	// inter     02     05     04     --     ??

insert:
	tnode->back = (offset) tback;
	tnode->next = (offset) tnext;
	ECX = tback->interval;
	tback->next = (offset) tnode;
	tnext->back = (offset) tnode;
	EAX += ECX;
	TEST(ECX, ECX);
	tback->interval = EAX;
	EAX = tnext->time[0];
	if ((signed) < 0)
		goto invalid_interval;

	// ここにくるこということは、挿入前のtbackのintervalが31bitで収まっていたことを意味する。
	// だから、挿入後のtnodeのintervalも31bitで収まる。
	EAX -= tnode->time[0];
	tnode->interval = EAX;
	return;

invalid_interval:
//	EAX = tnext->time[0];
	EAX -= tnode->time[0];
	ECX = tnext->time[4];
	if ((signed) < 0)
		goto skip1;
	EDX = tnext->time[8];
	/* ECX -= tnode->time[4] + CF */ SBB(ECX, tnode->time[4]);
	/* EDX -= tnode->time[8] + CF */ SBB(EDX, tnode->time[8]);
	ECX |= EDX;
	if (== 0)
		goto skip2;
skip1:
	EAX = 0x90000000;
skip2:
	tnode->interval = EAX;
	return;

comp_time:
	// 待ち時間は、現在時刻から見て、32bit以上。
	// 別に32bit以下でもいいが、遅い。
	/* (offset) tnext = &work->head */ LEA((offset) tnext, (int) work->head);
	EAX = tnode->time[0];
	do {
		ECX = tnode->time[4];
		CMP(EAX, tnext->time[0]);
		EDX = tnode->time[8];
		/* ECX -= tnext->time[4] + CF */ SBB(ECX, tnext->time[4]);
		(offset) tback = (offset) tnext; // 多分、この文は実行速度に影響しない(フラグストール)。
		/* EDX -= tnext->time[8] + CF */ SBB(EDX, tnext->time[8]);
		(offset) tnext = tnext->next;
	} while ((unsigned) >= 0);
	// ↑これはEDXしか見ていないことになるが、負になったかどうかが知りたいので、これでよい。

//	tnode < tback < tnext だった！(2000/05/25)
//	だから、一つ戻るべき
	(offset) tnext = (offset) tback;
	(offset) tback = tback->back;

	EBP = 0x90000000;
//	EAX = tnode->time[0];
	tback->interval = EBP;
	EAX -= tback->time[0];
	ECX = tnode->time[4];
	if ((signed) >= 0) { 
		EDX = tnode->time[8];
		/* ECX -= tback->time[4] + CF */ SBB(ECX, tback->time[4]);
		/* EDX -= tback->time[8] + CF */ SBB(EDX, tback->time[8]);
		ECX |= EDX;
		if (== 0)
			tback->interval = EAX;
	}
	tnode->back = (offset) tback;
	tnode->next = (offset) tnext;
	tback->next = (offset) tnode;
	tnext->back = (offset) tnode;
	EAX = tnext->time[0];
	tnode->interval = EBP;
	EAX -= tnode->time[0];
	ECX = tnext->time[4];
	if ((signed) >= 0) { 
		EDX = tnext->time[8];
		/* ECX -= tnode->time[4] + CF */ SBB(ECX, tnode->time[4]);
		/* EDX -= tnode->time[8] + CF */ SBB(EDX, tnode->time[8]);
		ECX |= EDX;
		if (== 0)
			tnode->interval = EAX;
	}
	return;
}

void near GetNowTime()
// work->status == 0のとき以外は呼ぶな
// CLIしておくこと
// 現在時刻を取得したからといって、割り込みをキャンセルしなければいけないなんてことはない。
// EDX:ECX:EAXに現在時刻を返す
// EDI:ESIに残りカウント(必ず正)
{
	// 残りカウントの算出方法 : (longcounter - 1) * 0x10000 + PIT0のカウント
	// (大きな数や0のときは、IRQ0が来ているかどうかによって、longcounterを小さく見てやる必要があるかもしれない)
	// 0でIRQが来ていなかったら・・・そんなことはありえない。絶対に来ている。が、longcountは減らしてはいけない。

	TimerWork *work == DS:0x0000;

	ESI = work->longcounter;
	EDI = 0;
	EAX = work->head.time[0];
	ECX = work->head.time[4];
	EAX -= ESI;
	EDX = work->head.time[8];
	/* ECX -= EDI + CF */ SBB(ECX, EDI);
	/* EDX -= CF */ SBB(EDX, EDI); 
	return;
}

void near GetTimeAndFixZero()
// work->status == 0のとき以外は呼ぶな
// EDI:ESIに残りカウント
// CLIしておくこと
// オーバーランに対応できるようにすべき
// work->longcounterが1で、しかもカウントアウトしていたら、普通の方法で現在時刻を出す
{
	// 残りカウントはhead.intervalに反映される。
	// これをやると、statusは2になる。

	TimerWork *work == DS:0x0000;

	ESI = work->longcounter;
	EDI = 0;
	ESI--;
	EAX = work->head.time[0];
	ECX = work->head.time[4];
	EAX -= ESI;
	EDX = work->head.time[8];
	/* ECX -= EDI + CF */ SBB(ECX, EDI);
	/* EDX -= CF */ SBB(EDX, EDI); 

	work->head.time[0] = EAX;
	work->head.time[4] = ECX;
	work->head.time[8] = EDX;
	work->status = 2;
	work->longcounter = 1;
	if ((signed) ESI < 0)
		goto interval_over;
	work->head.interval += ESI;
	if ((signed) < 0)
		goto interval_over;
//	asmout("JO #interval_over");
	return;

interval_over:
	work->head.interval = 0x90000000;
	return;
}

void near CancelTimer()
// DS:ESIのタイマーをキャンセル
// CLIしておくこと
{
	asmout("#TN_back EQU 56");

	TimerNode *tnode == DS:ESI, *tnext == DS:EDI, *tback == DS:EBX;
	// headに繋がっていて、かつ、status == 0なら、GetNowTime() + FixZero()してから切り離す
	asmout("TEST BYTE PTR DS:[ESI+#TN_back],0FH");
	if (== 0) {
	//	if ((offset) tnode == work->head.next) { // こんなのやっても、残り時間が増えるだけだから、いちいち割り込まない。
	//		if (work->status == 0) {
	//			GetTimeAndFixZero();
	//		}
	//	}
		(offset) tback = tnode->back;
		(offset) tnext = tnode->next;
		int interval == EAX, interval2 == ECX;
		interval = tnode->interval;
		tback->next = (offset) tnext;
		tnext->back = (offset) tback;
		asmout("MOV BYTE PTR DS:[ESI+#TN_back],02H"); // キャンセルされた
		interval2 = tback->interval;
		if ((signed) interval < 0)
			goto interval_over;
		if ((signed) interval2 < 0)
			goto interval_over;
		interval += interval2;
		if ((signed) < 0)
			goto interval_over;
		tback->interval = interval;
	}
	return;

interval_over:
	tback->interval = 0x90000000;
	return;
}

void far TimerSC_EOC()
// 終了コマンド
{
	EAX = [SS:ESP +  0];
	ECX = [SS:ESP +  4];
	EDX = [SS:ESP +  8];
	EBP = [SS:ESP + 12];
	ESI = [SS:ESP + 16];
	EDI = [SS:ESP + 20];
	ESP += 24; // CF = 0;
	POP(DS);
	return;
}

void near TimerSC_GetNode()
{
	asmout("#work_head EQU 256");
	asmout("#work_last EQU 320");
	asmout("#TN_back EQU 56");

	TimerWork *work == DS:0x0000;
	TimerNode *tnode == DS:ECX, *tnext == DS:EDX;
	PUSHFD();
	CLI();
	(offset) tnode = work->last.next;
	EAX = [FS:EBX + 8];
//	CMP((offset) tnode, (offset) &work->head);
	asmout("CMP ECX,#work_head");
	(offset) tnext = tnode->next;
	/* (false) */ if (==) {
		POPFD();
	//	STI();
		INT(0x03);
	}
	work->last.next = (offset) tnext;
//	tnext->back = (offset) &work->last;
	asmout("MOV DWORD PTR DS:[EDX+#TN_back],#work_last");
	POPFD();
//	STI();
	[FS:EBX + 4] = ECX; // 書き込み即値
	asmout("MOV BYTE PTR DS:[ECX+#TN_back],03H"); // 時刻すらセットされていない
	EBX += 8;
	asmout("JMP DWORD PTR DS:[TimerSC_JmpTbl+EAX]");
}

void near TimerSC_FreeNode()
{
	asmout("#work_last EQU 320");
	asmout("#TN_back EQU 56");

	TimerWork *work == DS:0x0000;
	TimerNode *tnode == DS:ECX, *tnext == DS:EDX;
	(offset) tnode = [FS:EBX + 4];
	EAX = [FS:EBX + 8];
	EBX += 8;
	PUSHFD();
	CLI();
	(offset) tnext = work->last.next;
	work->last.next = (offset) tnode;
//	tnode->back = (offset) &work->last;
	asmout("MOV DWORD PTR DS:[ECX+#TN_back],#work_last");
	tnode->next = (offset) tnext;
	tnext->back = (offset) tnode;
	POPFD();
//	STI();
	asmout("JMP DWORD PTR DS:[TimerSC_JmpTbl+EAX]");
}

void near TimerSC_SetNowTime()
// 現在時刻を知る
// 割り込み中に実行したら馬鹿。・・・でも多分、割り込み中でも平気
{
	TimerWork *work == DS:0x0000;

//	PUSH(EBX);
	PUSHFD();
	CLI();
	if (work->status == 0) {
		GetNowTime();
	} else {
		EAX = work->head.time[0];
		ECX = work->head.time[4];
		EDX = work->head.time[8];
	}
	POPFD();
//	STI();
//	POP(EBX);
	ESI = [FS:EBX + 16];
	[FS:EBX +  4] = EAX;
	[FS:EBX +  8] = ECX;
	[FS:EBX + 12] = EDX;
	EBX += 16;
	asmout("JMP DWORD PTR DS:[TimerSC_JmpTbl+ESI]");
}

void near TimerSC_SetTimer()
{
	SS0_struct *ss0 == /* SS:(- sizeof (SS0_struct)) */ SS:0xffffffc0;
	TimerWork *work == DS:0x0000;
	TimerNode *tnode == DS:ESI;
	(offset) tnode = [FS:EBX + 4];
	PUSH(EBX);
	PUSHFD();
	CLI();
	ss0->cli_count++;
	TimerRegister0();
	work->status = 0;
	
	ss0->cli_count--;
	/* (false) */ if (== 0) {
		PUSH(DS);
	//	PUSHAD();
		/* (ss0->clicall)() */ asmout("CALL FAR DWORD SS:[0FFFFFFC0H+038H]");
	//	POPAD();
		POP(DS);
	}
	POPFD();
//	STI();
	POP(EBX);
	EAX = [FS:EBX + 8];
	EBX += 8;
	asmout("JMP DWORD PTR DS:[TimerSC_JmpTbl+EAX]");
}

void near TimerSC_CancelTimer()
{
	TimerNode *tnode == DS:ESI;
	(offset) tnode = [FS:EBX + 4];
	EBP = EBX;
	PUSHFD();
	CLI();
	CancelTimer();
	POPFD();
//	STI();
	EAX = [FS:EBP + 8];
	/* EBX = EBP + 8 */ asmout("LEA EBX,[EBP+8]");
	asmout("JMP DWORD PTR DS:[TimerSC_JmpTbl+EAX]");
}

void near TimerSC_SetNowTimeNode()
// 現在時刻を知る
// 割り込み中に実行したら馬鹿。・・・でも多分、割り込み中でも平気
{
	TimerWork *work == DS:0x0000;
	TimerNode *tnode == DS:ESI;

//	PUSH(EBX);
	PUSHFD();
	CLI();
	if (work->status == 0) {
		GetNowTime();
	} else {
		EAX = work->head.time[0];
		ECX = work->head.time[4];
		EDX = work->head.time[8];
	}
	POPFD();
//	STI();
//	POP(EBX);
	(offset) tnode = [FS:EBX + 4];
	EDI = [FS:EBX + 8];
	EBX += 8;
	tnode->time[0] = EAX;
	tnode->time[4] = ECX;
	tnode->time[8] = EDX;
	asmout("JMP DWORD PTR DS:[TimerSC_JmpTbl+EDI]");
}

void near TimerSC_AddTimeNode()
{
	TimerWork *work == DS:0x0000;
	TimerNode *tnode == DS:ESI;

	(offset) tnode = [FS:EBX +  4];
	EAX = [FS:EBX +  8];
	ECX = [FS:EBX + 12];
	EDX = [FS:EBX + 16];
	EDI = [FS:EBX + 20];
	EBX += 20;
	tnode->time[0] += EAX;
	ADC(tnode->time[4], ECX);
	ADC(tnode->time[8], EDX);
	asmout("JMP DWORD PTR DS:[TimerSC_JmpTbl+EDI]");
}

void near TimerSC_SubTimeNode()
{
	TimerWork *work == DS:0x0000;
	TimerNode *tnode0 == DS:ESI, *tnode1 == DS:EDI;

	(offset) tnode1 = [FS:EBX +  8];
	(offset) tnode0 = [FS:EBX +  4];
	// tnode1 - tnode0
	EAX = tnode1->time[0];
	ECX = tnode1->time[4]; EAX -= tnode0->time[0];
	EDX = tnode1->time[8]; SBB(ECX, tnode0->time[4]);
	EDI = [FS:EBX + 24];   SBB(EDX, tnode0->time[8]);
	[FS:EBX + 12] = EAX;
	[FS:EBX + 16] = ECX;
	[FS:EBX + 20] = EDX;
	EBX += 24;
	asmout("JMP DWORD PTR DS:[TimerSC_JmpTbl+EDI]");
}

void near TimerSC_SetCallVectorNode()
{
	TimerWork *work == DS:0x0000;
	TimerNode *tnode == DS:ESI;

	(offset) tnode = [FS:EBX +  4];
	EAX = [FS:EBX +  8];
	ECX = [FS:EBX + 12];
	EDI = [FS:EBX + 16];
	EBX += 16;
	tnode->call_vector[0] = EAX;
	tnode->call_vector[4] = ECX;
	asmout("JMP DWORD PTR DS:[TimerSC_JmpTbl+EDI]");
}

void near TimerSC_SetMsgDataNode()
{
	TimerWork *work == DS:0x0000;
	TimerNode *tnode == DS:ESI;

	ESI = [FS:EBX +  4];
	ECX = [FS:EBX +  8];
	EBX += 12;

	#if (defined(DEBUG))
		TEST((char) tnode->back, 0x0f);
		if (== 0)
			INT(0x03);
	#endif

	do {
		EAX = [FS:EBX];
		EBX += 4;
		[DS:ESI] = EAX;
		ESI += 4;
		ECX--;
	} while (!= 0);
	EAX = [FS:EBX];
	asmout("JMP DWORD PTR DS:[TimerSC_JmpTbl+EAX]");
}

void near TimerSC_AddTimeNode2()
{
	TimerWork *work == DS:0x0000;
	TimerNode *tnode == DS:EDI;
	TimerNode *tnode2 == DS:ESI;

	(offset) tnode  = [FS:EBX +  4];
	(offset) tnode2 = [FS:EBX +  8];
	EAX = [FS:EBX + 12];
	ECX = [FS:EBX + 16];
	EAX += tnode->time[0];
	EDX = [FS:EBX + 20];
	ADC(ECX, tnode->time[4]);
	tnode2->time[0] = EAX;
	ADC(EDX, tnode->time[8]);
	EDI = [FS:EBX + 24];
	tnode2->time[4] = ECX;
	tnode2->time[8] = EDX;
	EBX += 24;
	asmout("JMP DWORD PTR DS:[TimerSC_JmpTbl+EDI]");
}

void near TimerInit();

void near TimerSC_Special()
{
	EAX = (int) [FS:EBX + 4]; // sub-cmd(4の倍数)
	if (EAX == 0) {
		// 初期化
		EBX += 16;
		PUSH(EBX);
		TimerInit();
		POP(EBX);
	} else if (EAX == 4) {
		// ノード構成と時間単位の提供
		// +08 : メッセージエリア長
		// +0c : far vectorエリアオフセット(6バイト)
		// +10 : タイムフィールド(12バイト)
		// +14 : reserve
		// +18 : 割り込み周期[sec]の2^64倍(8バイト)
		// +20 : 割り込み周波数[Hz]の2^32倍(8バイト)
		(int) [FS:EBX + 0x08] = 32;
		(int) [FS:EBX + 0x0c] = 32;
		(int) [FS:EBX + 0x10] = 40;
		(int) [FS:EBX + 0x14] = 0;
		(int) [FS:EBX + 0x18] = 0x5e572d0d;
		(int) [FS:EBX + 0x1c] = 0x004186af;
		(int) [FS:EBX + 0x20] = 0x26a0149a;
		(int) [FS:EBX + 0x24] = 1000;
		EBX += 40;
	} else if (EAX == 8) {
		// Intハンドラ情報
		// +08 : エントリ
		// +0c : reserve
		// +10 : reserve
		// +14 : reserve
		// +18 : reserve
		// +1c : reserve
		asmout("MOV EAX,OFFSET TimerInt");
		ECX = 0;
		(int) [FS:EBX + 0x08] = EAX;
		(int) [FS:EBX + 0x0c] = ECX;
		(int) [FS:EBX + 0x10] = ECX;
		(int) [FS:EBX + 0x14] = ECX;
		(int) [FS:EBX + 0x18] = ECX;
		(int) [FS:EBX + 0x1c] = ECX;
		EBX += 32;
	} else {
		INT(0x03);
	}
	EAX = [FS:EBX];
	asmout("JMP DWORD PTR DS:[TimerSC_JmpTbl+EAX]");
}

void near TimerInit()
//	汎用レジスタはすべて破壊
{
	TimerWork *work == DS:0x0000;
	unsigned short PIT_CMD == 0x0077, PIT0_COUNT == 0x0071;
	unsigned short PIC_master_IMR == 0x0002, PIC_master_OCW1 == 0x0002;

	asmout("TimerSC_JmpTbl EQU 64");
	asmout("#work_head EQU 256");
	asmout("#work_last EQU 320");
	asmout("#work_timeout EQU 384");
	asmout("#work_freenode EQU 448");

	asmout("#TN_singalbuf EQU 0");
	asmout("#TN_call_vector EQU 32");
	asmout("#TN_time EQU 40");
	asmout("#TN_inverval EQU 52");
	asmout("#TN_back EQU 56");
	asmout("#TN_next EQU 60");

//	PUSH(DS);
	PUSHFD();

//	AX = CS;
//	AX += 8;
//	DS = AX;

	ECX = 0;

	CLI();

	work->longcounter = 1;
	work->status = 0;

	EAX = 0;
	work->head.time[0] = EAX;
	work->head.time[4] = EAX;
	work->head.time[8] = 0x80000000;
	work->head.interval = 0x90000000 /* invalid_interval */;
//	/* work->head.back = &(work->last) */ asmout("MOV DWORD PTR DS:[#work_head+#TN_back],#work_last");
	/* work->head.next = &(work->last) */ asmout("MOV DWORD PTR DS:[#work_head+#TN_next],#work_last");

	EAX--;
	work->last.time[0] = EAX;
	work->last.time[4] = EAX;
	work->last.time[8] = EAX;
	/* work->last.back = &(work->head) */ asmout("MOV DWORD PTR DS:[#work_last+#TN_back],#work_head");
//	/* work->last.next = &(work->head) */ asmout("MOV DWORD PTR DS:[#work_last+#TN_next],#work_head");
	// work->last.call_vector[0] = (offset) ???;
	// work->last.call_vector[4] = (segment) ???; // 約一千兆年後にcallされる

	asmout("MOV EAX,#work_timeout");
	work->timeout.back = EAX;
	work->timeout.next = EAX;

// ECXを保存しろ

	TimerNode *tnode == DS:ESI, *tback == DS:EBX, *tnext == DS:EDI;

	EAX = 50 + 64;
//	(offset) tback = (offset) &work->freenode - sizeof (TimerNode);
//	(offset) tnode = (offset) &work->freenode;
//	(offset) tnext = (offset) &work->freenode + sizeof (TimerNode);
	asmout("MOV EBX,#work_freenode-64");
	asmout("MOV ESI,#work_freenode");
	asmout("MOV EDI,#work_freenode+64");

	work->last.next = (offset) tnode;

	do {
		tnode->back = (offset) tback;
		tnode->next = (offset) tnext;
		(offset) tback = (offset) tnode;
		(offset) tnode = (offset) tnext;
		(offset) tnext += sizeof (TimerNode);
		EAX--;
	} while (!= 0);

	work->head.back = (offset) tback;
//	work->freenode[0].back = (offset) &work->last;
	asmout("MOV DWORD PTR DS:[#work_freenode+#TN_back],#work_last");
	/* tback->next = (offset) &work->head */ asmout("MOV DWORD PTR DS:[EBX+#TN_back],#work_head");

	asmout("MOV EAX,TimerSC_JmpTbl");
	asmout("MOV DWORD PTR DS:[EAX+ 0],OFFSET TimerSC_EOC");
	asmout("MOV DWORD PTR DS:[EAX+ 4],OFFSET TimerSC_Special");
	asmout("MOV DWORD PTR DS:[EAX+ 8],OFFSET TimerSC_GetNode");
	asmout("MOV DWORD PTR DS:[EAX+12],OFFSET TimerSC_FreeNode");
	asmout("MOV DWORD PTR DS:[EAX+16],OFFSET TimerSC_SetTimer");
	asmout("MOV DWORD PTR DS:[EAX+20],OFFSET TimerSC_CancelTimer");
	asmout("MOV DWORD PTR DS:[EAX+24],OFFSET TimerSC_SetNowTime");
	asmout("MOV DWORD PTR DS:[EAX+28],OFFSET TimerSC_SetNowTimeNode");
	asmout("MOV DWORD PTR DS:[EAX+32],OFFSET TimerSC_AddTimeNode");
	asmout("MOV DWORD PTR DS:[EAX+36],OFFSET TimerSC_SubTimeNode");
	asmout("MOV DWORD PTR DS:[EAX+40],OFFSET TimerSC_SetCallVectorNode");
	asmout("MOV DWORD PTR DS:[EAX+44],OFFSET TimerSC_SetMsgDataNode");
	asmout("MOV DWORD PTR DS:[EAX+52],OFFSET TimerSC_AddTimeNode2");

//	CLI();

	// IRQ-00のマスクを解除して、割り込みを起こさせる。
	IN(AL, PIC_master_IMR);
	AL &= 0xfe; // IRQ-00許可
	OUT(PIC_master_OCW1, AL);

	IN(AL, 0x0042);
	TEST(AL, 0x20);
	AL = 0x34 /* PIT0, Low/High, mode2, binary */;
	OUT(PIT_CMD, AL);
	if (!= 0) {
		/* 8MHz系 */
		AL = 0xcd; // 設定値(1997)
		OUT(PIT0_COUNT, AL);
		AL = 0x07;
		OUT(PIT0_COUNT, AL);
		POPFD();
		return;
	}
	/* 5MHz系 */
	AL = 0x9a; // 設定値(2458)
	OUT(PIT0_COUNT, AL);
	AL = 0x09;
	OUT(PIT0_COUNT, AL);
	POPFD();
	return;

// 自分のシステムコールを使って、あほなTAPIやアプリのためにノードをとってやる。
// そういうことはInitがやるので、ここではやらない。
}
