/*	
 *	main.c
 *
 *	PceCom - P/ECE USBシリアルポートデバイスドライバ
 *	Copyright (C) 2005 Naoyuki Sawa
 *
 *	* Sat Jul 23 17:10: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_read;
	driver_object->MajorFunction[IRP_MJ_WRITE]          = dispatch_write;
	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;
	//
	NTSTATUS status;
	UNICODE_STRING device_name;
	WCHAR _device_name[64];
	DEVICE_EXTENSION* device_extension;

	/* デバイス名を作成します。(あとでデバイスエクステンションにコピーします。) */
	_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"));
		goto L_EXIT;
	}

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

	/* リムーブロックを初期化します。 */
	IoInitializeRemoveLock(&device_extension->remove_lock, 0, 0, 0);

	/* バッファアクセス方式を選択します。 */
	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;

	status = STATUS_SUCCESS;
L_EXIT:
	if(!NT_SUCCESS(status)) {
		delete_device(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)));

	/* このIRPのリムーブロックを獲得します。 */
	status = IoAcquireRemoveLock(&device_extension->remove_lock, irp);
	if(!NT_SUCCESS(status)) {
		KdPrint(("IoAcquireRemoveLock failed\n"));
		return complete_request(irp, status, 0);
	}

	/* 下位デバイスの処理を行い、完了を待ちます。
	 * IRPは未完了なので、このあとでもう一度IoCompleteRequest()が必要です。
	 */
	IoCopyCurrentIrpStackLocationToNext(irp);
	status = call_driver(device_extension->next_device_object, irp);
	if(!NT_SUCCESS(status)) {
		KdPrint(("call_driver failed\n"));
		IoReleaseRemoveLock(&device_extension->remove_lock, irp);
		return complete_request(irp, irp->IoStatus.Status, irp->IoStatus.Information);
	}

	switch(minor_function) {
	case IRP_MN_START_DEVICE:
		{
			/* コンフィギュレーションを選択します。 */
			status = select_configuration(device_object);
			if(!NT_SUCCESS(status)) {
				KdPrint(("select_configuration failed\n"));
				IoReleaseRemoveLock(&device_extension->remove_lock, irp);
				return complete_request(irp, status, 0);
			}

			/* IOCTLを初期化します。 */
			status = init_ioctl(device_object);
			if(!NT_SUCCESS(status)) {
				KdPrint(("init_ioctl failed\n"));
				IoReleaseRemoveLock(&device_extension->remove_lock, irp);
				return complete_request(irp, status, 0);
			}

			/* Read/Writeを初期化します。 */
			status = init_rdwr(device_object);
			if(!NT_SUCCESS(status)) {
				KdPrint(("init_rdwr failed\n"));
				IoReleaseRemoveLock(&device_extension->remove_lock, irp);
				return complete_request(irp, status, 0);
			}

			/* COMポートを登録します。 */
			status = register_serialcomm(device_object);
			if(!NT_SUCCESS(status)) {
				KdPrint(("register_serialcomm failed\n"));
				IoReleaseRemoveLock(&device_extension->remove_lock, irp);
				return complete_request(irp, status, 0);
			}

			break;
		}
	case IRP_MN_REMOVE_DEVICE:
		{
			/* 残っているIRPをすべてキャンセルします。 */
			cancel_all_irps(device_object);

			/* デバイスをクリーンアップします。 */
			delete_device(device_object);

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

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

			break;
		}
	}

	/* IRPの完了を継続します。 */
	if(minor_function == IRP_MN_REMOVE_DEVICE) {
		KdPrint(("IoReleaseRemoveLockAndWait start\n"));
		IoReleaseRemoveLockAndWait(&device_extension->remove_lock, irp);
		KdPrint(("IoReleaseRemoveLockAndWait done\n"));
	} else {
		IoReleaseRemoveLock(&device_extension->remove_lock, irp);
	}
	return complete_request(irp, irp->IoStatus.Status, irp->IoStatus.Information);
}

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

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"));

	/* Closeと対になるリムーブロックを獲得します。 */
	status = IoAcquireRemoveLock(&device_extension->remove_lock, stack_location->FileObject);
	if(!NT_SUCCESS(status)) {
		KdPrint(("IoAcquireRemoveLock failed\n"));
		return complete_request(irp, status, 0);
	}

	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"));

	/* Createと対になるリムーブロックを解放します。 */
	IoReleaseRemoveLock(&device_extension->remove_lock, stack_location->FileObject);

	return complete_request(irp, STATUS_SUCCESS, 0);
}

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

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);
}

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

/* デバイスをクリーンアップします。
 */
void
delete_device(DEVICE_OBJECT* device_object)
{
	DEVICE_EXTENSION* device_extension;

	/* デバイスが作成されていなければ、何もしません。 */
	if(!device_object) {
		return;
	}
	device_extension = device_object->DeviceExtension;

	/*---------- IRP_MN_START_DEVICEに対応 ----------*/

	/* COMポートを登録解除します。 */
	unregister_serialcomm(device_object);

	/* Read/Writeをクリーンアップします。 */
	exit_rdwr(device_object);

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

	/* コンフィギュレーションを未選択に戻します。 */
	deselect_configuration(device_object);

	/*--------------- AddDeviceに対応 ---------------*/

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

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

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

/* COMポートを登録します。
 */
NTSTATUS
register_serialcomm(DEVICE_OBJECT* device_object)
{
	DEVICE_EXTENSION* device_extension = device_object->DeviceExtension;
	//
	HANDLE key_handle = NULL;
	KEY_VALUE_FULL_INFORMATION* key_value_information = NULL;
	//
	NTSTATUS status;

	KdPrint(("register_serialcomm\n"));

	/* ポート名とシンボリックリンク名を格納するためのUNICODE_STRINGを初期化します。 */
	RtlInitEmptyUnicodeString(&device_extension->port_name,
		device_extension->_port_name, sizeof device_extension->_port_name);
	RtlInitEmptyUnicodeString(&device_extension->symbolic_link_name,
		device_extension->_symbolic_link_name, sizeof device_extension->_symbolic_link_name);

	/* Windowsによって自動的に割り当てられたポート名を取得します。 */
	status = IoOpenDeviceRegistryKey(
		device_extension->physical_device_object,
		PLUGPLAY_REGKEY_DEVICE,
		STANDARD_RIGHTS_ALL,
		&key_handle/*要解放*/);
	if(!NT_SUCCESS(status)) {
		KdPrint(("IoOpenDeviceRegistryKey failed\n"));
		goto L_EXIT;
	}
	key_value_information/*要解放*/ = query_value_key(key_handle, L"PortName");
	if(!key_value_information) {
		KdPrint(("query_value_key failed\n"));
		status = STATUS_UNSUCCESSFUL;
		goto L_EXIT;
	}
	if(key_value_information->Type != REG_SZ) {
		KdPrint(("Key type error\n"));
		status = STATUS_UNSUCCESSFUL;
		goto L_EXIT;
	}
	RtlAppendUnicodeToString(&device_extension->port_name,
		(PCWSTR)((int)key_value_information + key_value_information->DataOffset));
	KdPrint(("Port name = %wZ\n", &device_extension->port_name));

	/* シンボリックリンクを作成します。 */
	RtlAppendUnicodeToString(&device_extension->symbolic_link_name, L"\\??\\");
	RtlAppendUnicodeStringToString(&device_extension->symbolic_link_name, &device_extension->port_name);
	KdPrint(("Symbolic link name = %wZ\n", &device_extension->symbolic_link_name));
	status = IoCreateSymbolicLink(&device_extension->symbolic_link_name, &device_extension->device_name);
	if(!NT_SUCCESS(status)) {
		KdPrint(("IoCreateSymbolicLink failed\n"));
		goto L_EXIT;
	}

	/* レジストリのCOMポート一覧に登録します。 */
	status = RtlWriteRegistryValue(
		RTL_REGISTRY_DEVICEMAP,
		L"SERIALCOMM",
		device_extension->device_name.Buffer,
		REG_SZ,
		device_extension->port_name.Buffer,
		device_extension->port_name.Length + sizeof(WCHAR));
	if(!NT_SUCCESS(status)) {
		KdPrint(("RtlWriteRegistryValue failed\n"));
		goto L_EXIT;
	}

	status = STATUS_SUCCESS;
L_EXIT:
	if(!NT_SUCCESS(status)) {
		unregister_serialcomm(device_object);
	}
	if(key_value_information) {
		ExFreePool(key_value_information);
	}
	if(key_handle) {
		ZwClose(key_handle);
	}
	return status;
}

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

/* COMポートを登録解除します。
 */
void
unregister_serialcomm(DEVICE_OBJECT* device_object)
{
	DEVICE_EXTENSION* device_extension = device_object->DeviceExtension;

	KdPrint(("unregister_serialcomm\n"));

	/* レジストリのCOMポート一覧から登録解除します。 */
	if(device_extension->device_name.Buffer) {
		RtlDeleteRegistryValue(
			RTL_REGISTRY_DEVICEMAP,
			L"SERIALCOMM",
			device_extension->device_name.Buffer);
	}

	/* シンボリックリンクを削除します。 */
	if(device_extension->symbolic_link_name.Buffer) {
		IoDeleteSymbolicLink(&device_extension->symbolic_link_name);
	}
}

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

/* コンフィギュレーションを選択します。
 */
NTSTATUS
select_configuration(DEVICE_OBJECT* device_object)
{
	DEVICE_EXTENSION* device_extension = device_object->DeviceExtension;
	//
	URB* urb = NULL;
	USB_CONFIGURATION_DESCRIPTOR* configuration_descriptor = NULL;
	//
	NTSTATUS status;
	USBD_INTERFACE_LIST_ENTRY interface_list[1 + 1/*終端*/];
	USB_INTERFACE_DESCRIPTOR* interface_descriptor;
	USBD_INTERFACE_INFORMATION* interface_information;

	KdPrint(("select_configuration\n"));

	/* コンフィギュレーションディスクリプタを取得します。 */
	configuration_descriptor = get_configuration_descriptor(device_extension->next_device_object, 0);
	if(!configuration_descriptor) {
		KdPrint(("get_configuration_descriptor failed\n"));
		status = STATUS_UNSUCCESSFUL;
		goto L_EXIT;
	}

	/* SET_CONFIGURATIONの準備。 */
	memset(interface_list, 0, sizeof interface_list); /* 必要!! */
	interface_list[0].InterfaceDescriptor = USBD_ParseConfigurationDescriptorEx(
		configuration_descriptor,	/* ConfigurationDescriptor */
		configuration_descriptor,	/* StartPosition */
		-1,				/* InterfaceNumber */
		-1,				/* AlternateSetting */
		-1,				/* InterfaceClass */
		-1,				/* InterfaceSubClass */
		-1);				/* InterfaceProtocol */
	if(!interface_list[0].InterfaceDescriptor) {
		KdPrint(("USBD_ParseConfigurationDescriptorEx failed\n"));
		status = STATUS_UNSUCCESSFUL;
		goto L_EXIT;
	}
	urb = USBD_CreateConfigurationRequestEx(configuration_descriptor, interface_list);
	if(!urb) {
		KdPrint(("USBD_CreateConfigurationRequestEx failed\n"));
		status = STATUS_UNSUCCESSFUL;
		goto L_EXIT;
	}

	/* SET_CONFIGURATIONを発行します。 */
	status = call_usbd(device_extension->next_device_object, urb);
	if(!NT_SUCCESS(status)) {
		KdPrint(("call_usbd failed\n"));
		goto L_EXIT;
	}

	/* バルクIN/OUTパイプを取得しておきます。 */
	interface_information = interface_list[0].Interface;
	if(interface_information->NumberOfPipes < 2) {
		KdPrint(("Number of pipes error\n"));
		status = STATUS_UNSUCCESSFUL;
		goto L_EXIT;
	}
	device_extension->read_pipe  = interface_information->Pipes[0]; /* PDIUSBD12 EP2IN  */
	device_extension->write_pipe = interface_information->Pipes[1]; /* PDIUSBD12 EP2OUT */

	status = STATUS_SUCCESS;
L_EXIT:
	if(urb) {
		ExFreePool(urb);
	}
	if(configuration_descriptor) {
		ExFreePool(configuration_descriptor);
	}

	return status;
}

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

/* コンフィギュレーションを未選択に戻します。
 */
void
deselect_configuration(DEVICE_OBJECT* device_object)
{
	DEVICE_EXTENSION* device_extension = device_object->DeviceExtension;
	//
	URB urb;

	if(device_extension->next_device_object) {
		UsbBuildSelectConfigurationRequest(
			&urb,						/* Urb */
			sizeof(struct _URB_SELECT_CONFIGURATION),	/* Length */
			NULL);						/* ConfigurationDescriptor */
		call_usbd(device_extension->next_device_object, &urb);
	}
}

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

/* 残っているIRPをすべてキャンセルします。
 */
void
cancel_all_irps(DEVICE_OBJECT* device_object)
{
	DEVICE_EXTENSION* device_extension = device_object->DeviceExtension;
	IOCTL* ioctl = &device_extension->ioctl;
	//
	IRP* wait_on_mask_irp;

	/*-------------------- IOCTL --------------------*/

ENTER_CS;
	wait_on_mask_irp = ioctl->wait_on_mask_irp;
	ioctl->wait_on_mask_irp = NULL;
	if(wait_on_mask_irp) {
		IoSetCancelRoutine(wait_on_mask_irp, NULL); /* 保持しなくなったら、キャンセルルーチンを解除 */
	}
LEAVE_CS;
	if(wait_on_mask_irp) {
		IoReleaseRemoveLock(&device_extension->remove_lock, wait_on_mask_irp);
		complete_request(wait_on_mask_irp, STATUS_CANCELLED, 0);
	}

	/*-------------------- Read/Write --------------------*/

	abort_read(device_object);
	abort_write(device_object);
}

