/*	
 *	rx.c
 *
 *	PceCom - P/ECE USBシリアルポートデバイスドライバ
 *	Copyright (C) 2005 Naoyuki Sawa
 *
 *	* Wed Aug 03 21:57:00 JST 2005 Naoyuki Sawa
 *	- 1st リリース。
 */
#include "app.h"

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

static void rx_process_thread_routine(void* context);
static void rx_polling_thread_routine(void* context);

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

/* キューの中にあるIRPに対して、このキャンセルルーチンを設定します。
 */
static void
rx_cancel_from_queue_routine(DEVICE_OBJECT* device_object, IRP* irp)
{
	DEVICE_EXTENSION* device_extension = device_object->DeviceExtension;

	/* IRPをキューから取り除きます。 */
	IoSetCancelRoutine(irp, NULL);
	RemoveEntryList(&irp->Tail.Overlay.ListEntry);

	/* IoCancelIrp()が獲得したスピンロックを解放します。 */
	IoReleaseCancelSpinLock(irp->CancelIrql);

	/* IRPを完了させます。 */
	complete_request(irp, STATUS_CANCELLED, 0);
}

/* 現在のIRPに対して、このキャンセルルーチンを設定します。
 */
static void
rx_cancel_current_irp_routine(DEVICE_OBJECT* device_object, IRP* irp)
{
	DEVICE_EXTENSION* device_extension = device_object->DeviceExtension;

	ASSERT(device_extension->rx_current_irp == irp);

	/* IoCancelIrp()が獲得したスピンロックを解放します。 */
	IoReleaseCancelSpinLock(irp->CancelIrql);

	/* 受信処理スレッドがirp->Cancelを判断して、IRPを完了させます。 */
	KeSetEvent(&device_extension->rx_event_to_process, IO_NO_INCREMENT, FALSE);
}

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

NTSTATUS
rx_init(DEVICE_OBJECT* device_object)
{
	DEVICE_EXTENSION* device_extension = device_object->DeviceExtension;
	//
	NTSTATUS status;

	KdPrint(("rx_init\n"));

	/* 同期イベントを初期化します。 */
	KeInitializeEvent(&device_extension->rx_event_to_process, SynchronizationEvent, FALSE);
	KeInitializeEvent(&device_extension->rx_event_to_polling, SynchronizationEvent, FALSE);

	/* 受信バッファを確保します。 */
	status = rx_resize(device_object, device_extension->rx_pipe.MaximumTransferSize);
	if(!NT_SUCCESS(status)) {
		KdPrint(("ExAllocatePool failed\n"));
		goto L_EXIT;
	}

	/* IRPキューを初期化します。 */
	InitializeListHead(&device_extension->rx_queue);
	ASSERT(device_extension->rx_queue.Flink);

	/* 受信処理スレッドを開始します。 */
	device_extension->rx_process_thread = create_thread(rx_process_thread_routine, device_object);
	if(!device_extension->rx_process_thread) {
		KdPrint(("create_thread failed\n"));
		status = STATUS_UNSUCCESSFUL;
		goto L_EXIT;
	}

	/* 受信ポーリングスレッドを開始します。 */
	device_extension->rx_polling_thread = create_thread(rx_polling_thread_routine, device_object);
	if(!device_extension->rx_polling_thread) {
		KdPrint(("create_thread failed\n"));
		status = STATUS_UNSUCCESSFUL;
		goto L_EXIT;
	}

L_EXIT:

	if(!NT_SUCCESS(status)) {
		rx_exit(device_object);
	}

	return status;
}

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

void
rx_exit(DEVICE_OBJECT* device_object)
{
	DEVICE_EXTENSION* device_extension = device_object->DeviceExtension;

	KdPrint(("rx_exit\n"));

	///* 残っているIRPをすべて完了させます。 */
	//if(device_extension->rx_queue.Flink) {
	//	rx_abort(device_object);
	//}
	//→不要？

	/* 受信ポーリングスレッドを終了します。 */
	if(device_extension->rx_polling_thread) {
		delete_thread(device_extension->rx_polling_thread);
		device_extension->rx_polling_thread = NULL;
	}

	/* 受信処理スレッドを終了します。 */
	if(device_extension->rx_process_thread) {
		delete_thread(device_extension->rx_process_thread);
		device_extension->rx_process_thread = NULL;
	}

	/* 受信バッファを開放します。 */
	if(device_extension->rx_buffer) {
		ExFreePool(device_extension->rx_buffer);
		device_extension->rx_buffer = NULL;
	}
}

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

NTSTATUS
dispatch_rx(DEVICE_OBJECT* device_object, IRP* irp)
{
	DEVICE_EXTENSION* device_extension = device_object->DeviceExtension;

#ifndef QUIET
	KdPrint(("dispatch_rx\n"));
#endif /*QUIET*/

	/* IRPをキューへ追加した瞬間に所有権が無くなるので、先にマークしておきます。(必須!!) */
	IoMarkIrpPending(irp);

ENTER_CS;
	/* IRPをキューへ追加します。 */
	IoSetCancelRoutine(irp, rx_cancel_from_queue_routine);
	InsertTailList(&device_extension->rx_queue, &irp->Tail.Overlay.ListEntry);
LEAVE_CS;

	/* 空のIRPキューを検出して停止している(かも知れない)、受信処理スレッドを起こします。 */
	KeSetEvent(&device_extension->rx_event_to_process, IO_NO_INCREMENT, FALSE);

	return STATUS_PENDING;
}

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

NTSTATUS
rx_resize(DEVICE_OBJECT* device_object, int capacity)
{
	DEVICE_EXTENSION* device_extension = device_object->DeviceExtension;
	NTSTATUS status = STATUS_SUCCESS;
	//
	void* new_buffer;

	KdPrint(("rx_resize\n"));

ENTER_CS;

	/* 指定されたバッファサイズが現在のバッファサイズよりも大きければ、バッファを拡張します。
	 * 現在のバッファサイズよりも小さければ、何もしません。Serial IOCTLの仕様です。
	 */
	if(capacity > device_extension->rx_capacity) {
		new_buffer = ExAllocatePool(NonPagedPool, capacity); /* DISPATCH_LEVELでも、NonPagedPoolからの確保は可能 */
		if(new_buffer) {
			if(device_extension->rx_buffer) {
				memcpy(new_buffer, device_extension->rx_buffer, device_extension->rx_length);
				ExFreePool(device_extension->rx_buffer);
			}
			device_extension->rx_buffer = new_buffer;
			device_extension->rx_capacity = capacity;
		} else {
			KdPrint(("ExAllocatePool failed\n"));
			status = STATUS_UNSUCCESSFUL;
		}
	}

LEAVE_CS;

	return status;
}

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

void
rx_abort(DEVICE_OBJECT* device_object)
{
	DEVICE_EXTENSION* device_extension = device_object->DeviceExtension;
	//
	LIST_ENTRY irp_list;
	LIST_ENTRY* list_entry;
	IRP* irp;

	KdPrint(("rx_abort\n"));

	InitializeListHead(&irp_list);

ENTER_CS;

	/* IRPキューの中のIRPを全て取り出します。 */
	while(!IsListEmpty(&device_extension->rx_queue)) {
		list_entry = RemoveHeadList(&device_extension->rx_queue);
		irp = CONTAINING_RECORD(list_entry, IRP, Tail.Overlay.ListEntry);
		IoSetCancelRoutine(irp, NULL);
		InsertTailList(&irp_list, list_entry);
	}

	/* 現在のIRPがあれば、キャンセルを要求します。 */
	if(device_extension->rx_current_irp) {
		device_extension->rx_current_irp->Cancel = TRUE;
		KeSetEvent(&device_extension->rx_event_to_process, IO_NO_INCREMENT, FALSE);
	}

LEAVE_CS;

	/* IRPキューから取り出したIRPをすべて完了させます。 */
	while(!IsListEmpty(&irp_list)) {
		list_entry = RemoveHeadList(&irp_list);
		irp = CONTAINING_RECORD(list_entry, IRP, Tail.Overlay.ListEntry);
		complete_request(irp, STATUS_CANCELLED, 0);
	}
}

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

static
void rx_process_thread_routine(void* context)
{
	DEVICE_OBJECT* device_object = context;
	DEVICE_EXTENSION* device_extension = device_object->DeviceExtension;
	//
	KTIMER timeout_timer; /* 要停止 */
	//
	NTSTATUS status;
	LIST_ENTRY* list_entry;
	IRP* irp;
	IO_STACK_LOCATION* stack_location;
	unsigned char* buffer;
	int length;
	int copy_length;
	int total_length;
	int timeout_msec;
	LARGE_INTEGER timeout;
	//
	void* wait_objects[3];
	ASSERT(THREAD_WAIT_OBJECTS >= 3);

	KdPrint(("rx_process_thread_routine start\n"));

	/* タイムアウトタイマを初期化します。 */
	KeInitializeTimer(&timeout_timer);

	for(;;) {
		/* キューから次のIRPを取り出します。 */
		wait_objects[0] = &device_extension->terminate_event;
		wait_objects[1] = &device_extension->rx_event_to_process;
		for(;;) {
ENTER_CS;
			if(!IsListEmpty(&device_extension->rx_queue)) {
				list_entry = RemoveHeadList(&device_extension->rx_queue);
				irp = CONTAINING_RECORD(list_entry, IRP, Tail.Overlay.ListEntry);
				IoSetCancelRoutine(irp, rx_cancel_current_irp_routine);
				device_extension->rx_current_irp = irp;
			} else {
				irp = NULL;
			}
LEAVE_CS;
			if(irp) {
				break;
			}
			status = KeWaitForMultipleObjects(2, wait_objects, WaitAny, Executive, KernelMode, FALSE, NULL, NULL);
			if(!NT_SUCCESS(status)) {
				KdPrint(("KeWaitForMultipleObjects failed\n"));
				goto L_EXIT;
			}
			if(status == 0/*Terminate*/) {
				goto L_EXIT;
			}
		}
		stack_location = IoGetCurrentIrpStackLocation(irp);
		buffer = irp->AssociatedIrp.SystemBuffer;
		length = stack_location->Parameters.Read.Length;
		if(length <= 0) {
ENTER_CS;
			IoSetCancelRoutine(irp, NULL);
			device_extension->rx_current_irp = NULL;
LEAVE_CS;
			complete_request(irp, STATUS_SUCCESS, 0);
			continue; /* 次のIRPへ */
		}
		total_length = 0;

		/* タイムアウトを計算します。(※TODO: ReadIntervalTimeoutを考慮していない) */
ENTER_CS;
		if(!device_extension->timeouts.ReadTotalTimeoutMultiplier &&
		   !device_extension->timeouts.ReadTotalTimeoutConstant) {
			if(device_extension->timeouts.ReadIntervalTimeout == -1) {
				timeout_msec =  0; /* ブロックしない */
			} else {
				timeout_msec = -1; /* タイムアウトしない */
			}
		} else {
			timeout_msec = device_extension->timeouts.ReadTotalTimeoutMultiplier * length
				     + device_extension->timeouts.ReadTotalTimeoutConstant;
			if(timeout_msec <= 0) { /* オーバーフロー対策 */
				timeout_msec = INT_MAX;
			}
		}
LEAVE_CS;
		if(timeout_msec >= 0) {
			relative_timeout(&timeout, timeout_msec);
			KeSetTimer(&timeout_timer, timeout, NULL); /* 前回のタイマが動作中ならば、暗黙の停止後に再開します */
		} else {
			KeCancelTimer(&timeout_timer);
		}

		/* 取り出したIRPを処理します。 */
		wait_objects[0] = &device_extension->terminate_event;
		wait_objects[1] = &device_extension->rx_event_to_process;
		wait_objects[2] = &timeout_timer;
		for(;;) {
			if(irp->Cancel) {
ENTER_CS;
				IoSetCancelRoutine(irp, NULL);
				device_extension->rx_current_irp = NULL;
LEAVE_CS;
				complete_request(irp, STATUS_CANCELLED, 0);
				break; /* 次のIRPへ */
			}
			/*--------------------------------------------------------------------------------------------------*/

ENTER_CS;
			/* 受信バッファの有効サイズと、リクエストバッファの空きサイズのうち、小さい方をコピーサイズとします。 */
			copy_length = device_extension->rx_length;
			if(copy_length > length) {
				copy_length = length;
			}

			/* 受信バッファからリクエストバッファへコピーします。 */
			memcpy(buffer, device_extension->rx_buffer, copy_length);
			memmove(device_extension->rx_buffer, &device_extension->rx_buffer[copy_length], device_extension->rx_length - copy_length);
			device_extension->rx_length -= copy_length;
LEAVE_CS;

			/* 受信バッファの空きを待って停止している(かも知れない)、受信ポーリングスレッドを起こします。 */
			KeSetEvent(&device_extension->rx_event_to_polling, IO_NO_INCREMENT, FALSE);

			/* コピーした分、リクエストバッファのポインタとサイズを調整し、合計コピーサイズを増やします。 */
			buffer += copy_length;
			length -= copy_length;
			total_length += copy_length;

#if 0
			/* リクエストバッファ全体に受信完了したら、IRPを完了させます。 */
			if(!length) {
ENTER_CS;
				IoSetCancelRoutine(irp, NULL);
				device_extension->rx_current_irp = NULL;
LEAVE_CS;
				complete_request(irp, STATUS_SUCCESS, total_length);
				break; /* 次のIRPへ */
			}
#else
			/* 1バイトでも受信したら、IRPを即完了させます。(※TODO: ReadIntervalTimeoutを考慮していない) */
			if(total_length) {
ENTER_CS;
				IoSetCancelRoutine(irp, NULL);
				device_extension->rx_current_irp = NULL;
LEAVE_CS;
				complete_request(irp, STATUS_SUCCESS, total_length);
				break; /* 次のIRPへ */
			}
#endif

			/*--------------------------------------------------------------------------------------------------*/
			status = KeWaitForMultipleObjects(3, wait_objects, WaitAny, Executive, KernelMode, FALSE, NULL, NULL);
			if(!NT_SUCCESS(status)) {
				KdPrint(("KeWaitForMultipleObjects failed\n"));
ENTER_CS;
				IoSetCancelRoutine(irp, NULL);
				device_extension->rx_current_irp = NULL;
LEAVE_CS;
				complete_request(irp, STATUS_CANCELLED, 0);
				goto L_EXIT;
			}
			if(status == 0/*Terminate*/) {
ENTER_CS;
				IoSetCancelRoutine(irp, NULL);
				device_extension->rx_current_irp = NULL;
LEAVE_CS;
				complete_request(irp, STATUS_CANCELLED, 0);
				goto L_EXIT;
			}
			if(status == 2/*Timeout*/) {
ENTER_CS;
				IoSetCancelRoutine(irp, NULL);
				device_extension->rx_current_irp = NULL;
LEAVE_CS;
				//------------------------------------------------------------------------------------------------
				//if(total_length) {
				//	complete_request(irp, STATUS_SUCCESS, total_length); /* 1バイト以上受信してタイムアウト */
				//} else {
				//	complete_request(irp, STATUS_TIMEOUT, 0);            /* 1バイトも受信せずにタイムアウト */
				//}
				//------------------------------------------------------------------------------------------------
				//↓Read/Writeタイムアウト時の戻り値は、STATUS_TIMEOUTではなく、STATUS_SUCCESSが正解みたいです。
				//↓STATUS_TIMEOUTを返すと、Windows XPのSLIP接続が途中で止まって、OSが終了できなくなります。
				//↓STATUS_TIMEOUTはあくまで、KeWaitSingleObject/MultipleObjectsの戻り値として使われる値であって、
				//↓ドライバがI/Oマネージャに対して返す用途には使われない値なのかもしれません。
				//------------------------------------------------------------------------------------------------
				complete_request(irp, STATUS_SUCCESS, total_length);
				//------------------------------------------------------------------------------------------------

				break; /* 次のIRPへ */
			}
		}
	}

L_EXIT:

	/* タイムアウトタイマを確実に停止します。 */
	KeCancelTimer(&timeout_timer);

	KdPrint(("rx_process_thread_routine exit\n"));
	PsTerminateSystemThread(STATUS_SUCCESS);
}

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

static
void rx_polling_thread_routine(void* context)
{
	DEVICE_OBJECT* device_object = context;
	DEVICE_EXTENSION* device_extension = device_object->DeviceExtension;
	//
	unsigned char* buffer/*[MaximumTransferSize]*/ = NULL;
	int comm_event;
	//
	NTSTATUS status;
	int length;
	URB urb;
	//
	void* wait_objects[3];
	ASSERT(THREAD_WAIT_OBJECTS >= 3);

	KdPrint(("rx_polling_thread_routine start\n"));

	/* 一時バッファを確保します。 */
	buffer = ExAllocatePool(NonPagedPool, device_extension->rx_pipe.MaximumTransferSize);
	if(!buffer) {
		KdPrint(("ExAllocatePool failed\n"));
		goto L_EXIT;
	}

	for(;;) {
		/* ここに、今回のトランスファで発生したイベントを追加します。 */
		comm_event = 0;

ENTER_CS;
		ASSERT((device_extension->rx_length >= 0) && (device_extension->rx_length <= device_extension->rx_capacity));

		/* 受信バッファの空きを調べて、受信サイズを決定します。
		 * * 受信バッファサイズはパケットサイズの倍数でなければいけないことに注意してください!!
		 *   一回のトランスファは複数回のトランザクションで構成されますが、個々のトランザクションに注目したときに、
		 *   ホストの受信バッファサイズがデバイスの送信データサイズを下回っていてはいけません。
		 *   ホストの受信バッファサイズが不足すると、USBDへの受信要求が失敗したり、完了しなかったりするみたいです。
		 */
		length = device_extension->rx_capacity - device_extension->rx_length;	/* 受信バッファの空きサイズ */
		if(length > (int)device_extension->rx_pipe.MaximumTransferSize) {
			length = device_extension->rx_pipe.MaximumTransferSize;		/* 最大転送サイズを上限とする */
		}
		length &= ~(device_extension->rx_pipe.MaximumPacketSize - 1);		/* パケットサイズの倍数とする */
LEAVE_CS;

		/* 受信バッファに空きがあれば... */
		if(length) {
			/*{{この間に、受信バッファが読み出されて減ることはあっても、増えることは無いはずです。*/

			/* 転送を実行します。 */
			UsbBuildInterruptOrBulkTransferRequest(
				&urb,							/* Urb */
				sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER),		/* Length */
				device_extension->rx_pipe.PipeHandle,			/* PipeHandle */
				buffer,							/* TransferBuffer */
				NULL,							/* TransferBufferMDL */
				length,							/* TransferBufferLength */
				USBD_SHORT_TRANSFER_OK | USBD_TRANSFER_DIRECTION_IN,	/* TransferFlags */
				NULL);							/* Link */
			status = exec_transfer(device_object, &urb);
			if(!NT_SUCCESS(status)) {
				KdPrint(("Transfer failed\n"));
				goto L_EXIT;
			}

			/*}}この間に、受信バッファが読み出されて減ることはあっても、増えることは無いはずです。*/

			/* 実際に受信したサイズを取得します。 */
			length = urb.UrbBulkOrInterruptTransfer.TransferBufferLength;

			/* 受信データがあれば... */
			if(length) {
ENTER_CS;
				/* 一時バッファから受信バッファへコピーします。 */
				ASSERT(device_extension->rx_length + length <= device_extension->rx_capacity);
				memcpy(&device_extension->rx_buffer[device_extension->rx_length], buffer, length);
				device_extension->rx_length += length;
LEAVE_CS;
				/* 空の受信バッファを検出して停止している(かも知れない)、受信処理スレッドを起こします。 */
				KeSetEvent(&device_extension->rx_event_to_process, IO_NO_INCREMENT, FALSE);

				/* 今回のトランスファで発生したイベントを検出します。 */
				comm_event |= SERIAL_EV_RXCHAR;				/* 任意のキャラクタを受信した */
				if(memchr(buffer, device_extension->chars.EventChar, length)) {
					comm_event |= SERIAL_EV_RXFLAG;			/* イベントキャラクタを受信した */
				}
			}

		/* 受信バッファに空きが無ければ... */
		} else {

			/* 終了通知イベントか、または、受信バッファの変化を待ちます。 */
			wait_objects[0] = &device_extension->terminate_event;
			wait_objects[1] = &device_extension->rx_event_to_polling;
			status = KeWaitForMultipleObjects(2, wait_objects, WaitAny, Executive, KernelMode, FALSE, NULL, NULL);
			if(!NT_SUCCESS(status)) {
				KdPrint(("KeWaitForMultipleObjects failed\n"));
				goto L_EXIT;
			}
			if(status == 0/*Terminate*/) {
				goto L_EXIT;
			}
		}

		/* 今回のトランスファで発生したイベントに応じて、WaitOnMask IRPを完了します。 */
		assert_comm_event(device_object, comm_event);
	}

L_EXIT:

	/* 一時バッファを開放します。 */
	if(buffer) {
		ExFreePool(buffer);
	}

	KdPrint(("rx_polling_thread_routine exit\n"));
	PsTerminateSystemThread(STATUS_SUCCESS);
}
