/*	
 *	tx.c
 *
 *	PceEth - P/ECE NDIS-WDM ミニポートドライバ
 *	Copyright (C) 2005 Naoyuki Sawa
 *
 *	* Tue Aug 16 06:19:00 JST 2005 Naoyuki Sawa
 *	- 1st リリース。
 *	* Fri Aug 19 05:53:00 JST 2005 Naoyuki Sawa
 *	- 初期化直後の切断に伴う、Windows2000特有の問題回避処理を追加しました。
 */
#include "app.h"

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

static void tx_thread_routine(void* context);

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

NTSTATUS
tx_init(MINIPORT_ADAPTER* miniport_adapter)
{
	KdPrint(("tx_init called\n"));

	/* キューを初期化します。 */
	InitializeListHead(&miniport_adapter->tx_queue);

	/* 汎用イベントを初期化します。 */
	KeInitializeEvent(&miniport_adapter->tx_event, SynchronizationEvent, FALSE);

	/* スレッドを開始します。 */
	miniport_adapter->tx_thread = create_thread(tx_thread_routine, miniport_adapter);
	if(!miniport_adapter->tx_thread) {
		KdPrint(("create_thread failed\n"));
		goto L_ERR;
	}

	return STATUS_SUCCESS;

L_ERR:	/*-------------------------------------------------------------------*/

	if(miniport_adapter->tx_thread) {
		KeSetEvent(&miniport_adapter->abort_event, IO_NO_INCREMENT, FALSE);
		delete_thread(miniport_adapter->tx_thread);
		miniport_adapter->tx_thread = NULL;
	}

	return STATUS_UNSUCCESSFUL;
}

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

void
tx_exit(MINIPORT_ADAPTER* miniport_adapter)
{
	KdPrint(("tx_exit called\n"));

	/* スレッドの終了を待ちます。 */
	KeSetEvent(&miniport_adapter->abort_event, IO_NO_INCREMENT, FALSE);
	delete_thread(miniport_adapter->tx_thread);
	miniport_adapter->tx_thread = NULL;

	/* 未送信パケットを完了します。 */
	tx_cancel_packets(miniport_adapter);

	KdPrint(("tx_exit done\n"));
}

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

void
tx_cancel_packets(MINIPORT_ADAPTER* miniport_adapter)
{
	LIST_ENTRY* list_entry;
	NDIS_PACKET* packet;

	KdPrint(("tx_cancel_packets called\n"));

	/* 未送信パケットを完了します。 */
	for(;;) {
		list_entry = ExInterlockedRemoveHeadList(
			&miniport_adapter->tx_queue,
			&miniport_adapter->spin_lock);
		if(!list_entry) {
			break;
		}
		packet = CONTAINING_RECORD(list_entry, NDIS_PACKET, MiniportReserved);
		NdisMSendComplete(miniport_adapter->miniport_adapter_handle, packet, NDIS_STATUS_FAILURE);
		InterlockedIncrement(&miniport_adapter->xmit_error);
	}

	KdPrint(("tx_cancel_packets done\n"));
}

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

static void
tx_thread_routine(void* context)
{
	MINIPORT_ADAPTER* miniport_adapter = context;
	//
	unsigned char* transfer_buffer = NULL;
	//
	NTSTATUS status;
	int transfer_buffer_length;
	LIST_ENTRY* list_entry;
	NDIS_PACKET* packet;
	NDIS_BUFFER* buffer;
	int total_packet_length;
	void* virtual_address;
	int length;
	URB urb;
	//
	void* wait_objects[THREAD_WAIT_OBJECTS];
	ASSERT(THREAD_WAIT_OBJECTS >= 3);

	KdPrint(("tx_thread_routine called\n"));

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

	for(;;) {
		/* キューから、送信パケットを取り出します。 */
		wait_objects[0] = &miniport_adapter->abort_event;
		wait_objects[1] = &miniport_adapter->tx_event;
		for(;;) {
			list_entry = ExInterlockedRemoveHeadList(
				&miniport_adapter->tx_queue,
				&miniport_adapter->spin_lock);
			if(list_entry) {
				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/*abort_event*/) {
				goto L_EXIT;
			}
		}
		packet = CONTAINING_RECORD(list_entry, NDIS_PACKET, MiniportReserved);

		/* パケットのバッファを、送信バッファへ展開します。 */
		total_packet_length = 0;
		NdisQueryPacket(packet, NULL, NULL, &buffer, NULL);
		while(buffer) {
			NdisQueryBuffer(buffer, &virtual_address, &length);
			if(total_packet_length + length > transfer_buffer_length) {
				length = transfer_buffer_length - total_packet_length;
			}
			memcpy(&transfer_buffer[total_packet_length], virtual_address, length);
			total_packet_length += length;
			NdisGetNextBuffer(buffer, &buffer);
		}
		if(!total_packet_length) {
			NdisMSendComplete(miniport_adapter->miniport_adapter_handle, packet, NDIS_STATUS_SUCCESS);
			/* 空パケットは、成功として即座に完了させますが、カウントはしないことにします。 */
			continue; /* 次のパケット処理へ */
		}
		if(total_packet_length < 60) {
			/* 最小フレームサイズ未満の送信要求は、最小フレームサイズまでパディングして送出します。
			 * パディング領域のデータは何でも構いませんが、デバイス側でのデバッグを容易にするため、
			 * ゼロクリアしておくことにしました。(必須ではありません)
			 */
			memset(&transfer_buffer[total_packet_length], 0, (60 - total_packet_length));
			total_packet_length = 60;
			/* 継続 */
		}
		if(total_packet_length > 1514) {
			/* 最大フレームサイズ超過の送信要求は、送信エラーとしてカウントし、送出しません。 */
			KdPrint(("total_packet_length = %d\n", total_packet_length));
			InterlockedIncrement(&miniport_adapter->xmit_error);
			continue; /* 次のパケット処理へ */
		}
		KdPrint(("tx %02x-%02x-%02x-%02x-%02x-%02x %02x-%02x-%02x-%02x-%02x-%02x %04x %d\n",
			transfer_buffer[ 0],transfer_buffer[1],transfer_buffer[2],transfer_buffer[3],transfer_buffer[ 4],transfer_buffer[ 5],
			transfer_buffer[ 6],transfer_buffer[7],transfer_buffer[8],transfer_buffer[9],transfer_buffer[10],transfer_buffer[11],
			transfer_buffer[12]<<8|transfer_buffer[13],total_packet_length));

		/* USBへ送信します。 */
		/*{{同じコード*/
		UsbBuildInterruptOrBulkTransferRequest(
			&urb,							/* Urb */
			sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER),		/* Length */
			miniport_adapter->tx_pipe.PipeHandle,			/* PipeHandle */
			transfer_buffer,					/* TransferBuffer */
			NULL,							/* TransferBufferMDL */
			total_packet_length,					/* TransferBufferLength */
			USBD_TRANSFER_DIRECTION_OUT | USBD_SHORT_TRANSFER_OK,	/* TransferFlags */
			NULL);							/* Link */
		status = usb_transfer(miniport_adapter->next_device_object, &urb, &miniport_adapter->abort_event);
		if(!NT_SUCCESS(status)) {
			KdPrint(("exec_transfer failed\n"));
			NdisMSendComplete(miniport_adapter->miniport_adapter_handle, packet, NDIS_STATUS_FAILURE);
			InterlockedIncrement(&miniport_adapter->xmit_error);
			goto L_EXIT;
		}
		if(urb.UrbBulkOrInterruptTransfer.TransferBufferLength != total_packet_length) {
			KdPrint(("TransferBufferLength %d != %d\n", urb.UrbBulkOrInterruptTransfer.TransferBufferLength, total_packet_length));
			NdisMSendComplete(miniport_adapter->miniport_adapter_handle, packet, NDIS_STATUS_FAILURE);
			InterlockedIncrement(&miniport_adapter->xmit_error);
			goto L_EXIT;
		}
		/*}}同じコード*/

		/* エンドポイントのパケットサイズの倍数ならば、終端のために空パケットを送信します。 */
		total_packet_length %= miniport_adapter->tx_pipe.MaximumPacketSize;
		if(!total_packet_length) {
			/*{{同じコード*/
			UsbBuildInterruptOrBulkTransferRequest(
				&urb,							/* Urb */
				sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER),		/* Length */
				miniport_adapter->tx_pipe.PipeHandle,			/* PipeHandle */
				transfer_buffer,					/* TransferBuffer */
				NULL,							/* TransferBufferMDL */
				total_packet_length,					/* TransferBufferLength */
				USBD_TRANSFER_DIRECTION_OUT | USBD_SHORT_TRANSFER_OK,	/* TransferFlags */
				NULL);							/* Link */
			status = usb_transfer(miniport_adapter->next_device_object, &urb, &miniport_adapter->abort_event);
			if(!NT_SUCCESS(status)) {
				KdPrint(("exec_transfer failed\n"));
				NdisMSendComplete(miniport_adapter->miniport_adapter_handle, packet, NDIS_STATUS_FAILURE);
				InterlockedIncrement(&miniport_adapter->xmit_error);
				goto L_EXIT;
			}
			if(urb.UrbBulkOrInterruptTransfer.TransferBufferLength != total_packet_length) {
				KdPrint(("TransferBufferLength %d != %d\n", urb.UrbBulkOrInterruptTransfer.TransferBufferLength, total_packet_length));
				NdisMSendComplete(miniport_adapter->miniport_adapter_handle, packet, NDIS_STATUS_FAILURE);
				InterlockedIncrement(&miniport_adapter->xmit_error);
				goto L_EXIT;
			}
			/*}}同じコード*/
		}

		/* このパケットの送信を完了します。 */
		NdisMSendComplete(miniport_adapter->miniport_adapter_handle, packet, NDIS_STATUS_SUCCESS);
		InterlockedIncrement(&miniport_adapter->xmit_ok);
	}

L_EXIT:	/*-------------------------------------------------------------------*/

	/* 転送バッファを開放します。 */
	if(transfer_buffer) {
		ExFreePool(transfer_buffer);
		transfer_buffer = NULL;
	}

	/*{{2005/08/19 初期化直後の切断に伴う、Windows2000特有の問題回避処理を追加しました。*/
	/* * [NICデバイスドライバの初期化完了〜プロトコル/サービスとのバインド完了]の間のタイミング(?)でP/ECEを取り外すと、
	 *   OSがデバイスの消滅を判断できずに、送信要求パケットが送られて来てしまうみたいです。
	 *   送信スレッドは、USBDへの送信リクエスト失敗によりループを抜けてしまっているため、送信パケットが完了されません。
	 *   すると、デバイスドライバがアンロードできなくなり、OSの終了もできなくなってしまいます。(要、強制電源オフ)
	 *   miniport_halt()が呼ばれれば、明示的にキューを空にできるのですが、miniport_halt()も呼ばれないみたいです。
	 * * 上述の問題を回避するために、送信スレッドは、abort_event以外の要因でループを抜けた場合には、
	 *   すぐにはスレッドを終了せず、abort_eventがセットされるまで、送信要求パケットの完了処理を継続することにしました。
	 * * WindowsXPでは上述の問題は発生しませんが、同じ処理を追加しても無害だと思います。
	 *   Windows2000でも、環境によっては、問題が発生しない場合もあるみたいです。(要調査)
	 */
	KdPrint(("wait for abort_event start\n"));
	wait_objects[0] = &miniport_adapter->abort_event;
	wait_objects[1] = &miniport_adapter->tx_event;
	for(;;) {
		list_entry = ExInterlockedRemoveHeadList(
			&miniport_adapter->tx_queue,
			&miniport_adapter->spin_lock);
		if(list_entry) {
			packet = CONTAINING_RECORD(list_entry, NDIS_PACKET, MiniportReserved);
			NdisMSendComplete(miniport_adapter->miniport_adapter_handle, packet, NDIS_STATUS_FAILURE);
		} else {
			status = KeWaitForMultipleObjects(2, wait_objects, WaitAny, Executive, KernelMode, FALSE, NULL, NULL);
			if(!NT_SUCCESS(status)) {
				KdPrint(("KeWaitForMultipleObjects failed\n"));
				break;
			}
			if(status == 0/*abort_event*/) {
				break;
			}
		}
	}
	KdPrint(("wait for abort_event done\n"));
	/* * この時点で送信キューに残っているパケットは、tx_exit()から呼ばれるtx_cancel_packets()にて完了されるはずです。
	 *   上述の処理は、“送信要求パケットが残っているためにデバイスドライバのアンロードが起動しない”問題の回避策で、
	 *   デバイスドライバのアンロードが起動してしまえば、それ以降は通常通りのクリーンアップ処理にて完了させられます。
	 */
	/*}}2005/08/19 初期化直後の切断に伴う、Windows2000特有の問題回避処理を追加しました。*/

	KdPrint(("tx_thread_routine exit\n"));

	/* スレッドを終了します。 */
	PsTerminateSystemThread(STATUS_SUCCESS);
}
