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

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

NTSTATUS
DriverEntry(DRIVER_OBJECT* driver_object, UNICODE_STRING* registry_path)
{
	KdPrint(("DriverEntry\n"));

	/* ディスパッチルーチンを登録します。 */
	driver_object->DriverUnload                         = driver_unload;
	driver_object->DriverExtension->AddDevice           = add_device;
	driver_object->MajorFunction[IRP_MJ_PNP]            = dispatch_pnp;
	driver_object->MajorFunction[IRP_MJ_CREATE]         = dispatch_create;
	driver_object->MajorFunction[IRP_MJ_CLOSE]          = dispatch_close;
	driver_object->MajorFunction[IRP_MJ_READ]           = dispatch_rx;
	driver_object->MajorFunction[IRP_MJ_WRITE]          = dispatch_tx;
	driver_object->MajorFunction[IRP_MJ_DEVICE_CONTROL] = dispatch_ioctl;
	driver_object->MajorFunction[IRP_MJ_POWER]          = dispatch_power;

	return STATUS_SUCCESS;
}

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

void
driver_unload(DRIVER_OBJECT* driver_object)
{
	KdPrint(("driver_unload\n"));

	/** no job **/
}

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

NTSTATUS
add_device(DRIVER_OBJECT* driver_object, DEVICE_OBJECT* physical_device_object)
{
	static int device_index = -1;
	//
	DEVICE_OBJECT* device_object = NULL;
	DEVICE_EXTENSION* device_extension = NULL;
	//
	NTSTATUS status;
	WCHAR _device_name[64];
	UNICODE_STRING device_name;

	KdPrint(("add_device\n"));

	/* デバイス名を作成します。(あとでデバイスエクステンションに格納します。) */
	_snwprintf(_device_name, sizeof _device_name, L"\\Device\\PceCom%d", InterlockedIncrement(&device_index));
	RtlInitUnicodeString(&device_name, _device_name);
	KdPrint(("Device name = %wZ\n", &device_name));

	/* デバイスを作成します。 */
	status = IoCreateDevice(
		driver_object,			/* DriverObject */
		sizeof(DEVICE_EXTENSION),	/* DeviceExtensionSize */
		&device_name,			/* DeviceName */
		FILE_DEVICE_SERIAL_PORT,	/* DeviceType */
		0,				/* DeviceCharacteristics */
		TRUE,				/* Exclusive */
		&device_object);
	if(!NT_SUCCESS(status)) {
		KdPrint(("IoCreateDevice failed\n"));
		goto L_EXIT;
	}
	device_extension = device_object->DeviceExtension;

	/* デバイス、物理デバイスを格納します。 */
	device_extension->device_object = device_object;
	device_extension->physical_device_object = physical_device_object;

	/* デバイススタックに追加し、下位デバイスを格納します。 */
	device_extension->next_device_object = IoAttachDeviceToDeviceStack(device_object, physical_device_object);
	if(!device_extension->next_device_object) {
		KdPrint(("IoAttachDeviceToDeviceStack failed\n"));
		status = STATUS_UNSUCCESSFUL;
		goto L_EXIT;
	}

	/* デバイス名を格納します。 */
	RtlInitEmptyUnicodeString(&device_extension->device_name,
		device_extension->_device_name, sizeof device_extension->_device_name);
	RtlCopyUnicodeString(&device_extension->device_name, &device_name);

	/* バッファアクセス方式を選択します。 */
	device_object->Flags |= DO_BUFFERED_IO;

	/* 電力管理ルーチンがPASSIVE_LEVELで呼び出されるように設定します。
	 * ※本ドライバのコードセクションは、デフォルト配置＝NonPagedのはずなので、
	 *   電力管理ルーチンをDISPATCH_LEVELでも呼ばれても大丈夫なはずなのですが、
	 *   実際には、DO_POWER_PAGABLEを指定しておかないと、IRP_MJ_POWERが要求される
	 *   タイミングでバグチェックが発生して、システムが停止します。
	 *   なぜPASSIVE_LEVELでなければいけないのか、不明です。(下位ドライバの仕様?)
	 */
	device_object->Flags |= DO_POWER_PAGABLE;

	/* 初期化中マークを解除します。 */
	device_object->Flags &= ~DO_DEVICE_INITIALIZING;

L_EXIT:

	if(!NT_SUCCESS(status)) {
		if(device_extension && device_extension->next_device_object) {
			IoDetachDevice(device_extension->next_device_object);
			device_extension->next_device_object = NULL;
		}
		if(device_object) {
			IoDeleteDevice(device_object);
		}
	}

	return status;
}

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

NTSTATUS
dispatch_pnp(DEVICE_OBJECT* device_object, IRP* irp)
{
	DEVICE_EXTENSION* device_extension = device_object->DeviceExtension;
	IO_STACK_LOCATION* stack_location = IoGetCurrentIrpStackLocation(irp);
	int minor_function = stack_location->MinorFunction;
	//
	NTSTATUS status;

	KdPrint(("dispatch_pnp - %s\n", pnp_minor_function_name(minor_function)));

	switch(minor_function) {
	case IRP_MN_START_DEVICE:
		{
			int num_pipes;
			USBD_PIPE_INFORMATION pipes[2];

			/* 下位デバイスの処理を行います。 */
			status = call_next_driver(device_object, irp);
			if(!NT_SUCCESS(status)) {
				KdPrint(("call_next_driver failed\n"));
				return complete_request(irp, STATUS_UNSUCCESSFUL, 0);
			}

			/* 終了通知イベントを初期化します。
			 * * stop_device()にて、KEVENT.DISPATCHER_HEADER.Sizeを調べて、初期化済みor未初期化を判断します。
			 *   KEVENT.DISPATCHER_HEADER.Typeは初期化後も0のままみたいなので、Typeでは判断できないようです。
			 */
			KeInitializeEvent(&device_extension->terminate_event, NotificationEvent, FALSE);
			ASSERT(device_extension->terminate_event.Header.Size);

			/* COMポートを登録します。 */
			device_extension->serialcomm = register_serialcomm(
				device_extension->physical_device_object, device_extension->device_name.Buffer);
			if(!device_extension->serialcomm) {
				KdPrint(("register_serialcomm failed\n"));
				return complete_request(irp, STATUS_UNSUCCESSFUL, 0);
			}

			/* コンフィギュレーションを選択します。 */
			num_pipes = set_configuration(device_extension->next_device_object, pipes, 2);
			if(num_pipes != 2) {
				KdPrint(("set_configuration failed\n"));
				return complete_request(irp, STATUS_UNSUCCESSFUL, 0);
			}
			device_extension->rx_pipe = pipes[0]; /* 受信パイプ取得 */
			device_extension->tx_pipe = pipes[1]; /* 送信パイプ取得 */

			/* 受信処理を初期化します。 */
			status = rx_init(device_object);
			if(!NT_SUCCESS(status)) {
				KdPrint(("rx_init failed\n"));
				return complete_request(irp, STATUS_UNSUCCESSFUL, 0);
			}

			/* 送信処理を初期化します。 */
			status = tx_init(device_object);
			if(!NT_SUCCESS(status)) {
				KdPrint(("tx_init failed\n"));
				return complete_request(irp, STATUS_UNSUCCESSFUL, 0);
			}

			/* IOCTLを初期化します。 */
			status = ioctl_init(device_object);
			if(!NT_SUCCESS(status)) {
				KdPrint(("ioctl_init failed\n"));
				return complete_request(irp, STATUS_UNSUCCESSFUL, 0);
			}

			break;
		}
	case IRP_MN_REMOVE_DEVICE:
		{
			/* デバイスを停止します。
			 * ★下位デバイスへIRP_MN_REMOVE_DEVICEの処理を依頼する前に、送受信ポーリングIRPを停止しておかないと、
			 *   下位デバイスのIRP_MN_REMOVE_DEVICEが完了しません!!
			 */
			stop_device(device_object);

			/* 下位デバイスの処理を行います。 */
			status = call_next_driver(device_object, irp);
			if(!NT_SUCCESS(status)) {
				KdPrint(("call_next_driver failed\n"));
				return complete_request(irp, STATUS_UNSUCCESSFUL, 0);
			}

			/* デバイススタックから取り外します。 */
			if(device_extension->next_device_object) {
				IoDetachDevice(device_extension->next_device_object);
				device_extension->next_device_object = NULL;
			}

			/* デバイスを削除します。 */
			IoDeleteDevice(device_object);

			break;
		}
	case IRP_MN_QUERY_CAPABILITIES:
		{
			DEVICE_CAPABILITIES* device_capabilities = stack_location->Parameters.DeviceCapabilities.Capabilities;

			/* 下位デバイスの処理を行います。 */
			status = call_next_driver(device_object, irp);
			if(!NT_SUCCESS(status)) {
				KdPrint(("call_next_driver failed\n"));
				return complete_request(irp, STATUS_UNSUCCESSFUL, 0);
			}

			/* 下位デバイスによって格納されたデバイスケーパビリティに加えて、警告無しで取り外しできることをマークします。 */
			device_capabilities->SurpriseRemovalOK = TRUE;

			break;
		}
	default:
		{
			/* 下位デバイスの処理を行います。 */
			status = call_next_driver(device_object, irp);
			if(!NT_SUCCESS(status)) {
				KdPrint(("call_next_driver failed\n"));
				return complete_request(irp, STATUS_UNSUCCESSFUL, 0);
			}

			break;
		}
	}

	/* IRPの完了を継続します。 */
	return complete_request(irp, irp->IoStatus.Status, irp->IoStatus.Information);
}

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

NTSTATUS
dispatch_power(DEVICE_OBJECT* device_object, IRP* irp)
{
	DEVICE_EXTENSION* device_extension = device_object->DeviceExtension;
	IO_STACK_LOCATION* stack_location = IoGetCurrentIrpStackLocation(irp);

	KdPrint(("dispatch_power\n"));

	/* 下位デバイスへ流します。
	 * * 電力管理ルーチンは、通常のIRPと異なり、
	 * - PoStartNextPowerIrp()が必要であること。
	 * - IoCallDriver()でなくPoCallDriver()を使うこと。
	 *   に注意してください。
	 */
	PoStartNextPowerIrp(irp);
	IoSkipCurrentIrpStackLocation(irp);
	return PoCallDriver(device_extension->next_device_object, irp);
}

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

NTSTATUS
dispatch_create(DEVICE_OBJECT* device_object, IRP* irp)
{
	DEVICE_EXTENSION* device_extension = device_object->DeviceExtension;
	IO_STACK_LOCATION* stack_location = IoGetCurrentIrpStackLocation(irp);
	//
	NTSTATUS status;

	KdPrint(("dispatch_create\n"));

	return complete_request(irp, STATUS_SUCCESS, 0);
}

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

NTSTATUS
dispatch_close(DEVICE_OBJECT* device_object, IRP* irp)
{
	DEVICE_EXTENSION* device_extension = device_object->DeviceExtension;
	IO_STACK_LOCATION* stack_location = IoGetCurrentIrpStackLocation(irp);
	//
	NTSTATUS status;

	KdPrint(("dispatch_close\n"));

	return complete_request(irp, STATUS_SUCCESS, 0);
}

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

/* 下位デバイスへIRPをパススルーし、完了を待ちます。
 * IRPは未完了なので、このあとでもう一度IoCompleteRequest()が必要です。
 */
NTSTATUS
call_next_driver(DEVICE_OBJECT* device_object, IRP* irp)
{
	DEVICE_EXTENSION* device_extension = device_object->DeviceExtension;
	//
	NTSTATUS status;

	KdPrint(("call_next_driver\n"));

	IoCopyCurrentIrpStackLocationToNext(irp);
	status = call_driver(device_extension->next_device_object, irp);
	if(!NT_SUCCESS(status)) {
		KdPrint(("call_driver failed\n"));
		/* FALLTHRU */
	}

	return status;
}

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

/* デバイスを停止します。
 */
void
stop_device(DEVICE_OBJECT* device_object)
{
	DEVICE_EXTENSION* device_extension = device_object->DeviceExtension;

	KdPrint(("stop_device\n"));

	/* 終了通知イベントをシグナル状態にします。 */
	if(device_extension->terminate_event.Header.Size) {
		KeSetEvent(&device_extension->terminate_event, IO_NO_INCREMENT, FALSE);
	}

	/* COMポートを登録解除します。 */
	if(device_extension->serialcomm) {
		unregister_serialcomm(device_extension->serialcomm);
		device_extension->serialcomm = NULL;
	}

	/* 受信処理をクリーンアップします。 */
	rx_exit(device_object);

	/* 送信処理をクリーンアップします。 */
	tx_exit(device_object);

	/* IOCTLをクリーンアップします。 */
	ioctl_exit(device_object);

	/* コンフィギュレーションを未選択に戻します。 */
	unset_configuration(device_extension->next_device_object);
}

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

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

NTSTATUS
exec_transfer(DEVICE_OBJECT* device_object, URB* urb)
{
	DEVICE_EXTENSION* device_extension = device_object->DeviceExtension;
	DEVICE_OBJECT* usbd = device_extension->next_device_object;
	//
	NTSTATUS status;
	IRP* irp;
	IO_STACK_LOCATION* stack_location;
	KEVENT event;
	IO_STATUS_BLOCK status_block;
	int pending;
	//
	void* wait_objects[3];
	ASSERT(THREAD_WAIT_OBJECTS >= 3);

	/* IRP完了通知イベントを初期化します。 */
	KeInitializeEvent(&event, NotificationEvent, FALSE); /* SynchronizationEvent不可 */

	/* IRPを作成します。(完了時に自動的に解放されるので、明示的な解放は不要です) */
	irp = IoBuildDeviceIoControlRequest(
		IOCTL_INTERNAL_USB_SUBMIT_URB,	/* IoControlCode */
		usbd,				/* DeviceObject */
		NULL,				/* InputBuffer */
		0,				/* InputBufferLength */
		NULL,				/* OutputBuffer */
		0,				/* OutputBufferLength */
		TRUE,				/* InternalDeviceIoControl */
		&event,				/* Event */
		&status_block);			/* IoStatusBlock */
	if(!irp) {
		KdPrint(("IoBuildDeviceIoControlRequest failed\n"));
		status = STATUS_UNSUCCESSFUL;
		goto L_EXIT;
	}
	stack_location = IoGetNextIrpStackLocation(irp); /* IoGetCurrentIrpStackLocation()ではありません!! */
	stack_location->Parameters.Others.Argument1 = urb;

	/* 同期IRPのキャンセル…『Microsoft WDM プログラミング』p.237〜239参照 */
	IoSetCompletionRoutine(irp, transfer_completion_routine, &event, TRUE, TRUE, TRUE);
	pending = IoCallDriver(usbd, irp) == STATUS_PENDING;
	if(pending) {	/* ←無くても可 */
		wait_objects[0] = &device_extension->terminate_event;
		wait_objects[1] = &event;
		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*/) {
			KdPrint(("Cancel transfer\n"));
			IoCancelIrp(irp);
		}
		KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); /* transfer_completion_routine()によるEvent set待ち */
	}		/* ←無くても可 */
	KeClearEvent(&event); /* transfer_completion_routine()によるEvent setを解除する */
	IoCompleteRequest(irp, IO_NO_INCREMENT); /* STATUS_MORE_PROCESSING_REQUIREDによって中断しているIRPの完了処理を再開する */
	if(pending) {	/* ←必須!! */
		/* ★重要: IoBuildDeviceIoControlRequest()に指定したEventが、IRPの完了時にシグナル状態になるのは、
		 *         IoCallDriver()からSTATUS_PENDINGが返された場合だけです。
		 *         STATUS_PENDING以外が返された場合は、IRPが完了しても、Eventはシグナル状態になりません!!
		 */
		KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); /* IoBuildDeviceIoControlRequest()によるEvent set待ち */
	}		/* ←必須!! */
	status = status_block.Status;

L_EXIT:

	return status;
}

