/*	
 *	tx.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 tx_process_thread_routine(void* context);
static void tx_polling_thread_routine(void* context);

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

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

	ASSERT(device_extension->tx_current_irp == irp);

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

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

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

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

	KdPrint(("tx_init\n"));

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

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

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

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

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

L_EXIT:

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

	return status;
}

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

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

	KdPrint(("tx_exit\n"));

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

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

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

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

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

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

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

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

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

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

	return STATUS_PENDING;
}

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

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

	KdPrint(("tx_resize\n"));

ENTER_CS;

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

LEAVE_CS;

	return status;
}

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

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

	KdPrint(("tx_abort\n"));

	InitializeListHead(&irp_list);

ENTER_CS;

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

	/* 現在のIRPがあれば、キャンセルを要求します。 */
	if(device_extension->tx_current_irp) {
		device_extension->tx_current_irp->Cancel = TRUE;
		KeSetEvent(&device_extension->tx_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 tx_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(("tx_process_thread_routine start\n"));

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

	for(;;) {
		/* キューから次のIRPを取り出します。 */
		wait_objects[0] = &device_extension->terminate_event;
		wait_objects[1] = &device_extension->tx_event_to_process;
		for(;;) {
ENTER_CS;
			if(!IsListEmpty(&device_extension->tx_queue)) {
				list_entry = RemoveHeadList(&device_extension->tx_queue);
				irp = CONTAINING_RECORD(list_entry, IRP, Tail.Overlay.ListEntry);
				IoSetCancelRoutine(irp, tx_cancel_current_irp_routine);
				device_extension->tx_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.Write.Length;
		if(length <= 0) {
ENTER_CS;
			IoSetCancelRoutine(irp, NULL);
			device_extension->tx_current_irp = NULL;
LEAVE_CS;
			complete_request(irp, STATUS_SUCCESS, 0);
			continue; /* 次のIRPへ */
		}
		total_length = 0;

		/* タイムアウトを計算します。 */
ENTER_CS;
		if(!device_extension->timeouts.WriteTotalTimeoutMultiplier &&
		   !device_extension->timeouts.WriteTotalTimeoutConstant) {
			timeout_msec = -1; /* タイムアウトしない */
		} else {
			timeout_msec = device_extension->timeouts.WriteTotalTimeoutMultiplier * length
				     + device_extension->timeouts.WriteTotalTimeoutConstant;
			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->tx_event_to_process;
		wait_objects[2] = &timeout_timer;
		for(;;) {
			if(irp->Cancel) {
ENTER_CS;
				IoSetCancelRoutine(irp, NULL);
				device_extension->tx_current_irp = NULL;
LEAVE_CS;
				complete_request(irp, STATUS_CANCELLED, 0);
				break; /* 次のIRPへ */
			}
			/*--------------------------------------------------------------------------------------------------*/

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

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

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

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

			/* リクエストバッファ全体を送信完了したら、IRPを完了させます。 */
			if(!length) {
ENTER_CS;
				IoSetCancelRoutine(irp, NULL);
				device_extension->tx_current_irp = NULL;
LEAVE_CS;
				complete_request(irp, STATUS_SUCCESS, total_length);
				break; /* 次のIRPへ */
			}

			/*--------------------------------------------------------------------------------------------------*/
			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->tx_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->tx_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->tx_current_irp = NULL;
LEAVE_CS;
				//------------------------------------------------------------------------------------------------
				//if(total_length) {
				//	complete_request(irp, STATUS_SUCCESS, total_length); /* 1バイト以上送信してタイムアウト */
				//} else {
				//	complete_request(irp, STATUS_TIMEOUT, 0);            /* 1バイトも送信せずにタイムアウト */
				//}
				//------------------------------------------------------------------------------------------------
				complete_request(irp, STATUS_SUCCESS, total_length);
				//------------------------------------------------------------------------------------------------

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

L_EXIT:

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

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

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

/* 同期IRPのキャンセル…『Microsoft WDM プログラミング』p.237〜239参照 */
static NTSTATUS
urb_completion_routine(DEVICE_OBJECT* device_object, IRP* irp, void* context)
{
	KEVENT* event = context;
	KeSetEvent(event, IO_NO_INCREMENT, FALSE);
	return STATUS_MORE_PROCESSING_REQUIRED;
}

static
void tx_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(("tx_polling_thread_routine start\n"));

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

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

ENTER_CS;
		ASSERT((device_extension->tx_length >= 0) && (device_extension->tx_length <= device_extension->tx_capacity));

		/* 送信サイズを決定します。 */
		length = device_extension->tx_length;				/* 送信バッファの有効サイズ */
		if(length > (int)device_extension->tx_pipe.MaximumTransferSize) {
			length = device_extension->tx_pipe.MaximumTransferSize;	/* 最大転送サイズを上限とする */
		}
		if(length) {
			/* 送信バッファから一時バッファへコピーします。 */
			memcpy(buffer, device_extension->tx_buffer, length);
			memmove(device_extension->tx_buffer, &device_extension->tx_buffer[length], device_extension->tx_length - length);
			device_extension->tx_length -= length;
		}
LEAVE_CS;

		/* 送信データがあれば... */
		if(length) {
			/* 送信IRP(URB)を作成します。 */
			UsbBuildInterruptOrBulkTransferRequest(
				&urb,							/* Urb */
				sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER),		/* Length */
				device_extension->tx_pipe.PipeHandle,			/* PipeHandle */
				buffer,							/* TransferBuffer */
				NULL,							/* TransferBufferMDL */
				length,							/* TransferBufferLength */
				USBD_SHORT_TRANSFER_OK | USBD_TRANSFER_DIRECTION_OUT,	/* TransferFlags */
				NULL);							/* Link */
			status = exec_transfer(device_object, &urb);
			if(!NT_SUCCESS(status)) {
				KdPrint(("Transfer failed\n"));
				goto L_EXIT;
			}

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

			/* 送信処理が成功した場合、送信サイズが最大転送サイズ以下ならば、全部一度で送信できているはずです。
			 * * シリアル通信などのデータ区切りの無い通信では、要求よりも小さなサイズでの送信成功も有り得ます。
			 *   しかし、USBの場合は、トランスファ単位でのデータの区切りが意味を持っているので、
			 *   バスドライバが勝手に送信サイズを減らして、送信を成功させることは有り得ないはずです。
			 * * …と思うのですけれど、確証が持てないので、念のためフリービルドでもチェックすることにします(^^;
			 */
			if(length != urb.UrbBulkOrInterruptTransfer.TransferBufferLength) {
				KdPrint(("Write length error\n"));
				goto L_EXIT;
			}

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

			/* 今回のトランスファで発生したイベントを検出します。 */
			if(!device_extension->tx_length) {
				comm_event |= SERIAL_EV_TXEMPTY;			/* 送信バッファが空になった */
			}

		/* 送信データが無ければ... */
		} else {

			/* 終了通知イベントか、または、送信バッファの変化を待ちます。 */
			wait_objects[0] = &device_extension->terminate_event;
			wait_objects[1] = &device_extension->tx_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(("tx_polling_thread_routine exit\n"));
	PsTerminateSystemThread(STATUS_SUCCESS);
}
