/*	
 *	rx.c
 *
 *	PceEth - P/ECE NDIS-WDM ミニポートドライバ
 *	Copyright (C) 2005 Naoyuki Sawa
 *
 *	* Tue Aug 16 06:19:00 JST 2005 Naoyuki Sawa
 *	- 1st リリース。
 */
#include "app.h"

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

#define NUM_PACKETS	5	/* 調整可 */

static void rx_thread_routine(void* context);

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

NTSTATUS
rx_init(MINIPORT_ADAPTER* miniport_adapter)
{
	NDIS_STATUS ndis_status;
	int i;
	LIST_ENTRY* list_entry;
	NDIS_PACKET* packet;

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

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

	/* 受信パケットとバッファのプールを確保します。 */
	NdisAllocatePacketPool(
		&ndis_status,
		&miniport_adapter->rx_packet_pool_handle,
		NUM_PACKETS,
		PROTOCOL_RESERVED_SIZE_IN_PACKET);
	if(ndis_status != NDIS_STATUS_SUCCESS) {
		KdPrint(("NdisAllocatePacketPool failed\n"));
		goto L_ERR;
	}
	NdisAllocateBufferPool(
		&ndis_status,
		&miniport_adapter->rx_buffer_pool_handle,
		NUM_PACKETS);
	if(ndis_status != NDIS_STATUS_SUCCESS) {
		KdPrint(("NdisAllocateBufferPool failed\n"));
		goto L_ERR;
	}

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

	return STATUS_SUCCESS;

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

	if(miniport_adapter->rx_packet_pool_handle &&
	   miniport_adapter->rx_buffer_pool_handle) {
		rx_recall_packets(miniport_adapter);
	}

	if(miniport_adapter->rx_buffer_pool_handle) {
		NdisFreeBufferPool(miniport_adapter->rx_buffer_pool_handle);
		miniport_adapter->rx_buffer_pool_handle = NULL;
	}

	if(miniport_adapter->rx_packet_pool_handle) {
		NdisFreePacketPool(miniport_adapter->rx_packet_pool_handle);
		miniport_adapter->rx_packet_pool_handle = NULL;
	}

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

	return STATUS_UNSUCCESSFUL;
}

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

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

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

	/* 受信パケットを回収します。 */
	rx_recall_packets(miniport_adapter);

	/* 受信パケットとバッファのプールを開放します。 */
	NdisFreeBufferPool(miniport_adapter->rx_buffer_pool_handle);
	NdisFreePacketPool(miniport_adapter->rx_packet_pool_handle);

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

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

NDIS_PACKET*
rx_allocate_packet(MINIPORT_ADAPTER* miniport_adapter, const void* data, int length)
{
	NDIS_STATUS ndis_status;
	NDIS_PACKET* packet;
	NDIS_BUFFER* buffer;
	void* virtual_address;
	KIRQL irql;

	KeAcquireSpinLock(&miniport_adapter->spin_lock, &irql);

	/*-------------------------------------------------------------------*/

	/* バッファのメモリを確保します。 */
	virtual_address = ExAllocatePool(NonPagedPool, length);
	if(virtual_address) {

		/* バッファを確保します。 */
		NdisAllocateBuffer(
			&ndis_status,
			&buffer,
			miniport_adapter->rx_buffer_pool_handle,
			virtual_address,
			length);
		if(ndis_status == NDIS_STATUS_SUCCESS) {

			/* パケットを確保します。 */
			NdisAllocatePacket(
				&ndis_status,
				&packet,
				miniport_adapter->rx_packet_pool_handle);
			if(ndis_status == NDIS_STATUS_SUCCESS) {

				/* パケットにバッファを関連付けます。 */
				NdisChainBufferAtBack(packet, buffer);

				/* データをコピーします。 */
				memcpy(virtual_address, data, length);

				/* Ethernetフレームのヘッダサイズを指示します。 */
				NDIS_SET_PACKET_HEADER_SIZE(packet, 14);

				/* 後でNdisMIndicateReceivePacket()を使って、パケットを上位ドライバへ通知しますが、
				 * そのとき、上位ドライバが非同期的に処理するか、同期的に処理するかを選択できます。
				 * - NDIS_PACKET.Status=NDIS_STATUS_SUCCESSを設定しておくと、非同期処理となります。
				 * - NDIS_PACKET.Status=NDIS_STATUS_RESOURCESを設定しておくと、同期処理となります。
				 * もしも、同期処理を選択した場合、MiniportReturnPacket()はコールバックされません。
				 * 詳しくは、NdisMIndicateReceivePacket()のHelpを参照してください。
				 */
				NDIS_SET_PACKET_STATUS(packet, NDIS_STATUS_SUCCESS);

				/* 成功です。 */
				goto L_EXIT;
			}

			/* 失敗なので、バッファをプールへ戻します。 */
			NdisFreeBuffer(buffer);
			buffer = NULL;
		}

		/* 失敗なので、バッファのメモリを開放します。 */
		ExFreePool(virtual_address);
		virtual_address = NULL;
	}

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

	KeReleaseSpinLock(&miniport_adapter->spin_lock, irql);

	return packet;
}

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

void
rx_free_packet(MINIPORT_ADAPTER* miniport_adapter, NDIS_PACKET* packet)
{
	NDIS_BUFFER* buffer;
	void* virtual_address;
	int length;
	KIRQL irql;

	KeAcquireSpinLock(&miniport_adapter->spin_lock, &irql);

	/*-------------------------------------------------------------------*/

	/* パケットのバッファを取得します。 */
	NdisQueryPacket(packet, NULL, NULL, &buffer, NULL);
	ASSERT(buffer);

	/* バッファのメモリを取得します。 */
	NdisQueryBuffer(buffer, &virtual_address, &length);
	ASSERT(virtual_address);

	/* パケットをプールへ戻します。 */
	NdisFreePacket(packet);

	/* バッファをプールへ戻します。 */
	NdisFreeBuffer(buffer);

	/* バッファのメモリを解放します。 */
	ExFreePool(virtual_address);

	/*-------------------------------------------------------------------*/

	KeReleaseSpinLock(&miniport_adapter->spin_lock, irql);
}

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

void
rx_recall_packets(MINIPORT_ADAPTER* miniport_adapter)
{
	int i;
	NDIS_PACKET* packet[NUM_PACKETS];

	KdPrint(("rx_recall_packets called\n"));
	ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);

	/* 未使用の受信パケットを、全て回収します。 */
	for(i = 0; i < NUM_PACKETS; i++) {
		for(;;) {
			packet[i] = rx_allocate_packet(miniport_adapter, NULL, 0);
			if(packet[i]) {
				KdPrint(("recalled %d/%d\n", i + 1, NUM_PACKETS));
				break;
			}
			KeWaitForSingleObject(&miniport_adapter->rx_event, Executive, KernelMode, FALSE, NULL);
		}
	}

	/* 全て回収した後、再び、プールへ戻します。 */
	for(i = 0; i < NUM_PACKETS; i++) {
		rx_free_packet(miniport_adapter, packet[i]);
	}

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

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

static void
rx_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(("rx_thread_routine called\n"));

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

	for(;;) {
		/* USBから受信します。 */
		UsbBuildInterruptOrBulkTransferRequest(
			&urb,							/* Urb */
			sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER),		/* Length */
			miniport_adapter->rx_pipe.PipeHandle,			/* PipeHandle */
			transfer_buffer,					/* TransferBuffer */
			NULL,							/* TransferBufferMDL */
			transfer_buffer_length,					/* TransferBufferLength */
			USBD_TRANSFER_DIRECTION_IN | 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"));
			InterlockedIncrement(&miniport_adapter->rcv_error);
			goto L_EXIT;
		}
		total_packet_length = urb.UrbBulkOrInterruptTransfer.TransferBufferLength;
		if(!total_packet_length) {
			/* 空パケットは、上位ドライバへ通知せず、カウントもしないことにします。 */
			continue; /* 次のパケット処理へ */
		}
		if((total_packet_length < 60) || (total_packet_length > 1514)) {
			/* 最小フレームサイズ未満、または、最大フレームサイズ超過のパケットは、
			 * 受信エラーとしてカウントし、上位ドライバへ通知しません。
			 */
			KdPrint(("total_packet_length = %d\n", total_packet_length));
			InterlockedIncrement(&miniport_adapter->rcv_error);
			continue; /* 次のパケット処理へ */
		}
		KdPrint(("rx %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));

		/* プールから、受信パケットを取り出します。 */
		wait_objects[0] = &miniport_adapter->abort_event;
		wait_objects[1] = &miniport_adapter->rx_event;
		for(;;) {
			packet = rx_allocate_packet(miniport_adapter, transfer_buffer, total_packet_length);
			if(packet) {
				break;
			}
			status = KeWaitForMultipleObjects(2, wait_objects, WaitAny, Executive, KernelMode, FALSE, NULL, NULL);
			if(!NT_SUCCESS(status)) {
				KdPrint(("KeWaitForMultipleObjects failed\n"));
				InterlockedIncrement(&miniport_adapter->rcv_error);
				goto L_EXIT;
			}
			if(status == 0/*abort_event*/) {
				InterlockedIncrement(&miniport_adapter->rcv_error);
				goto L_EXIT;
			}
		}

		/* 上位ドライバ(NDIS/プロトコルドライバ)へ通知します。 */
		NdisMIndicateReceivePacket(miniport_adapter->miniport_adapter_handle, &packet, 1);
		InterlockedIncrement(&miniport_adapter->rcv_ok);
	}

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

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

	/* 受信スレッドは、abort_eventがセットされるのを待たず、すぐに終了しても問題ありませんが、
	 * 送信スレッドの実装に合わせて、受信スレッドもabort_eventを待って終了することにしました。
	 */
	KdPrint(("wait for abort_event start\n"));
	KeWaitForSingleObject(&miniport_adapter->abort_event, Executive, KernelMode, FALSE, NULL);
	KdPrint(("wait for abort_event done\n"));

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

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