/*	
 *	util.c
 *
 *	WDMデバイスドライバ汎用ルーチン
 *	Copyright (C) 2005 Naoyuki Sawa
 *
 *	* Wed Aug 03 21:57:00 JST 2005 Naoyuki Sawa
 *	- 1st リリース。
 */
#include "util.h"

//-----------------------------------------------------------------------------------------------------------------------------
// * Win32アプリケーションに、COMポートとして認識させるには、以下のレジストリキーを登録します。
//
//	HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM
//
//	[-]マイコンピュータ
//	   [-]HKEY_LOCAL_MACHINE
//	      [-]HARDWARE
//	         [-]DEVICEMAP
//	            [-]SERIALCOMM [ab](標準)          REG_SZ (値の設定なし)
//	                          [ab]\Device\Serial0 REG_SZ COM1
//	                          [ab]\Device\PceCom  REG_SZ COM? <=これを追加します!!
//	                              ~~~~~~~~~~~~~~         ~~~~
//	                              厳密には、左はデバイス名である必要はありません。
//	                              他のデバイスからの登録との重複を防ぐために、
//	                              デバイス名を使う慣習になっているのだと思います。
//
// - RtlWriteRegistryValue()を使うと、レジストリキーのフルパスを指定しなくても、
//   かんたんにこのレジストリキーにアクセスすることができます。
//
// - このレジストリキーは、PCを再起動するたびに自動的にクリアされます。
//   従って、ドライバのAddDevice処理にて、毎回登録する必要があります。
//   ただし、デバイスが取り外されたときに、自動的にクリアはされません。
//   従って、ドライバのRemoveDevice処理にて、明示的に登録解除する必要があります。
//-----------------------------------------------------------------------------------------------------------------------------

//-----------------------------------------------------------------------------------------------------------------------------
// * 不正なデバイスドライバを使った後などに、Windowsシステムの自動COMポート割り当てがズレてしまう場合があります。
//   通常、最初からCOM1、COM2が存在し、追加されたCOMポートはCOM3、COM4、...となるはずですが、
//   COM3を飛ばしてCOM4になったり、不正なデバイスドライバを使うたびにCOM5、COM6、...と後ろへズレて行ったりします。
//
// - この症状を解決するには、以下のレジストリキーを手作業で修正します。
//
//	HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\COM Name Arbiter
//
//	[-]マイコンピュータ
//	   [-]HKEY_LOCAL_MACHINE
//	      [-]HARDWARE
//	         [-]DEVICEMAP
//	            [-]SERIALCOMM [01]ComDB REG_BINARY 03 00 00 00 00 00 00 00
//	                                               00 00 00 00 00 00 00 00
//	                                               00 00 00 00 00 00 00 00
//	                                               00 00 00 00 00 00 00 00
//
//   このレジストリキーは、予約されているCOMポートの、ビットマスク配列となっています。
//   COM1、COM2が存在する場合、最初のバイトのbit0とbit1がOn、残りのデータはすべて0です。
//   すなわち、レジストリキーの内容は、{03,00,00,...,00} (全32バイト) となっています。
//
// - 不正なデバイスドライバのアンロードに失敗した場合などに、予約ビットが増えていく場合があります。
//   増えてしまった予約ビットを手作業でゼロに戻すことにより、自動COMポート割り当てのズレを元に戻すことができます。
//   ★手修正の際に、誤って、総バイト数(32バイト)を増やしたり減らしたりしないよう、注意してください!!★
//-----------------------------------------------------------------------------------------------------------------------------

//-----------------------------------------------------------------------------------------------------------------------------
// * 完了待ちを行わないIoCallDriver()の直前に、IoMarkIrpPending()を行う理由は、次のとおりです。
//   下位のIoCallDriver()がSTATUS_PENDINGを返した場合は、このレベルでもIoMarkIrpPending()を呼ぶ必要があるようです。
//   ただし、IoCallDriver()を呼んだ瞬間に、irpは無効になっていると考えなければいけません。
//   (無効にならないケースは、完了ルーチンがセットアップされ、かつ、完了ルーチンがSTATUS_MORE_PROCESSING_REQUIREDを返した場合のみです。)
//   次のようなコードは誤りです。
//
//	[誤りの例]
//
//	status = IoCallDriver(device_extension->next_device_object, irp);
//	if(status == STATUS_PENDING) {
//		/* <<このタイミングで割り込みが発生し、irpが完了し、irpの実体が消えるかもしれない>> */
//		IoMarkIrpPending(irp); /* 存在しないかもしれないirpにアクセス!! */
//	}
//
//   従って、あらかじめ念のためにIoMarkIrpPending(irp)を呼んでおく、という方法を採ります。(DDKのbulkusbサンプルより)
//   PendingしないIRPに対してIoMarkIrpPending(irp)を呼ぶことは、特に問題ありません。
//   明らかにシグナル状態になっている完了イベントを、システムが明示的に待ち、わずかに性能低下するだけです。
//-----------------------------------------------------------------------------------------------------------------------------

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

/* 相対タイムアウト値を1ミリ秒単位で指定します。
 * [note]
 *	* KeWaitForSingleObject()のタイムアウト指定等に用いるLONG_INTEGER構造体は、
 *	  100ナノ秒単位の負値で、現在時刻からの相対時間を示します。(正値は絶対時刻)
 *	  プログラム中では1ミリ秒単位の方がわかり易いので、変換関数を用意しました。
 */
LARGE_INTEGER*
relative_timeout(LARGE_INTEGER* timeout, int msec)
{
	timeout->QuadPart = -(10000i64 * msec);
	//                  | ++++++++------------- 1ミリ秒→100ナノ秒単位変換
	//                  +---------------------- 相対時間は負値で指定します
	return timeout;
}

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

typedef struct _NAME_TABLE {
	int code;
	const char* name;
} NAME_TABLE;

static const char*
_lookup_name_table(int code, const NAME_TABLE* name_table, int table_size)
{
	static char tmp[16];
	//
	int i;
	const char* name;

	for(i = 0; i < table_size; i++) {
		if(name_table[i].code == code) {
			return name_table[i].name;
		}
	}

	_snprintf(tmp, sizeof tmp, "$%x", code);
	return tmp;
}

#define lookup_name_table(code, name_table) \
	_lookup_name_table((code), (name_table), sizeof (name_table) / sizeof *(name_table))

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

static const NAME_TABLE pnp_minor_function_name_table[] = {
{IRP_MN_START_DEVICE,                "IRP_MN_START_DEVICE"                },
{IRP_MN_QUERY_REMOVE_DEVICE,         "IRP_MN_QUERY_REMOVE_DEVICE"         },
{IRP_MN_REMOVE_DEVICE,               "IRP_MN_REMOVE_DEVICE"               },
{IRP_MN_CANCEL_REMOVE_DEVICE,        "IRP_MN_CANCEL_REMOVE_DEVICE"        },
{IRP_MN_STOP_DEVICE,                 "IRP_MN_STOP_DEVICE"                 },
{IRP_MN_QUERY_STOP_DEVICE,           "IRP_MN_QUERY_STOP_DEVICE"           },
{IRP_MN_CANCEL_STOP_DEVICE,          "IRP_MN_CANCEL_STOP_DEVICE"          },
{IRP_MN_QUERY_DEVICE_RELATIONS,      "IRP_MN_QUERY_DEVICE_RELATIONS"      },
{IRP_MN_QUERY_INTERFACE,             "IRP_MN_QUERY_INTERFACE"             },
{IRP_MN_QUERY_CAPABILITIES,          "IRP_MN_QUERY_CAPABILITIES"          },
{IRP_MN_QUERY_RESOURCES,             "IRP_MN_QUERY_RESOURCES"             },
{IRP_MN_QUERY_RESOURCE_REQUIREMENTS, "IRP_MN_QUERY_RESOURCE_REQUIREMENTS" },
{IRP_MN_QUERY_DEVICE_TEXT,           "IRP_MN_QUERY_DEVICE_TEXT"           },
{IRP_MN_FILTER_RESOURCE_REQUIREMENTS,"IRP_MN_FILTER_RESOURCE_REQUIREMENTS"},
{IRP_MN_READ_CONFIG,                 "IRP_MN_READ_CONFIG"                 },
{IRP_MN_WRITE_CONFIG,                "IRP_MN_WRITE_CONFIG"                },
{IRP_MN_EJECT,                       "IRP_MN_EJECT"                       },
{IRP_MN_SET_LOCK,                    "IRP_MN_SET_LOCK"                    },
{IRP_MN_QUERY_ID,                    "IRP_MN_QUERY_ID"                    },
{IRP_MN_QUERY_PNP_DEVICE_STATE,      "IRP_MN_QUERY_PNP_DEVICE_STATE"      },
{IRP_MN_QUERY_BUS_INFORMATION,       "IRP_MN_QUERY_BUS_INFORMATION"       },
{IRP_MN_DEVICE_USAGE_NOTIFICATION,   "IRP_MN_DEVICE_USAGE_NOTIFICATION"   },
{IRP_MN_SURPRISE_REMOVAL,            "IRP_MN_SURPRISE_REMOVAL"            },
};

const char*
pnp_minor_function_name(int code)
{
	return lookup_name_table(code, pnp_minor_function_name_table);
}

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

static const NAME_TABLE serial_ioctl_name_table[] = {
{IOCTL_SERIAL_SET_BAUD_RATE,    "IOCTL_SERIAL_SET_BAUD_RATE"    },
{IOCTL_SERIAL_SET_QUEUE_SIZE,   "IOCTL_SERIAL_SET_QUEUE_SIZE"   },
{IOCTL_SERIAL_SET_LINE_CONTROL, "IOCTL_SERIAL_SET_LINE_CONTROL" },
{IOCTL_SERIAL_SET_BREAK_ON,     "IOCTL_SERIAL_SET_BREAK_ON"     },
{IOCTL_SERIAL_SET_BREAK_OFF,    "IOCTL_SERIAL_SET_BREAK_OFF"    },
{IOCTL_SERIAL_IMMEDIATE_CHAR,   "IOCTL_SERIAL_IMMEDIATE_CHAR"   },
{IOCTL_SERIAL_SET_TIMEOUTS,     "IOCTL_SERIAL_SET_TIMEOUTS"     },
{IOCTL_SERIAL_GET_TIMEOUTS,     "IOCTL_SERIAL_GET_TIMEOUTS"     },
{IOCTL_SERIAL_SET_DTR,          "IOCTL_SERIAL_SET_DTR"          },
{IOCTL_SERIAL_CLR_DTR,          "IOCTL_SERIAL_CLR_DTR"          },
{IOCTL_SERIAL_RESET_DEVICE,     "IOCTL_SERIAL_RESET_DEVICE"     },
{IOCTL_SERIAL_SET_RTS,          "IOCTL_SERIAL_SET_RTS"          },
{IOCTL_SERIAL_CLR_RTS,          "IOCTL_SERIAL_CLR_RTS"          },
{IOCTL_SERIAL_SET_XOFF,         "IOCTL_SERIAL_SET_XOFF"         },
{IOCTL_SERIAL_SET_XON,          "IOCTL_SERIAL_SET_XON"          },
{IOCTL_SERIAL_GET_WAIT_MASK,    "IOCTL_SERIAL_GET_WAIT_MASK"    },
{IOCTL_SERIAL_SET_WAIT_MASK,    "IOCTL_SERIAL_SET_WAIT_MASK"    },
{IOCTL_SERIAL_WAIT_ON_MASK,     "IOCTL_SERIAL_WAIT_ON_MASK"     },
{IOCTL_SERIAL_PURGE,            "IOCTL_SERIAL_PURGE"            },
{IOCTL_SERIAL_GET_BAUD_RATE,    "IOCTL_SERIAL_GET_BAUD_RATE"    },
{IOCTL_SERIAL_GET_LINE_CONTROL, "IOCTL_SERIAL_GET_LINE_CONTROL" },
{IOCTL_SERIAL_GET_CHARS,        "IOCTL_SERIAL_GET_CHARS"        },
{IOCTL_SERIAL_SET_CHARS,        "IOCTL_SERIAL_SET_CHARS"        },
{IOCTL_SERIAL_GET_HANDFLOW,     "IOCTL_SERIAL_GET_HANDFLOW"     },
{IOCTL_SERIAL_SET_HANDFLOW,     "IOCTL_SERIAL_SET_HANDFLOW"     },
{IOCTL_SERIAL_GET_MODEMSTATUS,  "IOCTL_SERIAL_GET_MODEMSTATUS"  },
{IOCTL_SERIAL_GET_COMMSTATUS,   "IOCTL_SERIAL_GET_COMMSTATUS"   },
{IOCTL_SERIAL_XOFF_COUNTER,     "IOCTL_SERIAL_XOFF_COUNTER"     },
{IOCTL_SERIAL_GET_PROPERTIES,   "IOCTL_SERIAL_GET_PROPERTIES"   },
{IOCTL_SERIAL_GET_DTRRTS,       "IOCTL_SERIAL_GET_DTRRTS"       },
{IOCTL_SERIAL_LSRMST_INSERT,    "IOCTL_SERIAL_LSRMST_INSERT"    },
{IOCTL_SERIAL_CONFIG_SIZE,      "IOCTL_SERIAL_CONFIG_SIZE"      },
{IOCTL_SERIAL_GET_COMMCONFIG,   "IOCTL_SERIAL_GET_COMMCONFIG"   },
{IOCTL_SERIAL_SET_COMMCONFIG,   "IOCTL_SERIAL_SET_COMMCONFIG"   },
{IOCTL_SERIAL_GET_STATS,        "IOCTL_SERIAL_GET_STATS"        },
{IOCTL_SERIAL_CLEAR_STATS,      "IOCTL_SERIAL_CLEAR_STATS"      },
{IOCTL_SERIAL_GET_MODEM_CONTROL,"IOCTL_SERIAL_GET_MODEM_CONTROL"},
{IOCTL_SERIAL_SET_MODEM_CONTROL,"IOCTL_SERIAL_SET_MODEM_CONTROL"},
{IOCTL_SERIAL_SET_FIFO_CONTROL, "IOCTL_SERIAL_SET_FIFO_CONTROL" },
};

const char*
serial_ioctl_name(int code)
{
	return lookup_name_table(code, serial_ioctl_name_table);
}

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

/* IOリクエストを完了します。
 */
NTSTATUS
complete_request(IRP* irp, NTSTATUS status, int information)
{
	/* IRP構造体に結果を格納します。 */
	irp->IoStatus.Status = status;
	irp->IoStatus.Information = information;

	/* IOリクエストの完了をマークします。 */
	IoCompleteRequest(irp, IO_NO_INCREMENT);

	return status;
}

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

/* IOリクエスト完了時に呼び出されるコールバック関数です。
 */
static NTSTATUS
call_driver_completion_routine(DEVICE_OBJECT* device_object, IRP* irp, void* context)
{
	KEVENT* event = context;

	//if(irp->PendingReturned) {
	//	IoMarkIrpPending(irp);
	//}
	//→STATUS_MORE_PROCESSING_REQUIREDを返す完了ルーチンでは不要です。

	/* イベントをシグナル状態にして、完了を通知します。 */
	KeSetEvent(event, IO_NO_INCREMENT, FALSE);

	return STATUS_MORE_PROCESSING_REQUIRED; /* 必須!! */
}

/* IOリクエストを発行して、完了を待ちます。
 * [note]
 *	* 完了ルーチンを設定するので、IoSkipCurrentIrpStackLocation()との併用は不可です。
 *
 *		IoSkipCurrentIrpStackLocation(irp);       // 誤り!!
 *		status = call_driver(device_object, irp);
 *
 *	  代りに、IoCopyCurrentIrpStackLocationToNext()を使ってください。
 *
 *		IoCopyCurrentIrpStackLocationToNext(irp); // 正しい
 *		status = call_driver(device_object, irp);
 *
 *	* 下位ドライバのIoCompleteRequest()を、STATUS_MORE_PROCESSING_REQUIREDによって中断した状態で処理を返します。
 *	  このIRPは完了していないので、call_driver()呼び出し側にてもう一度、IoCompleteRequest()を行う必要があります。
 *
 *		call_driver(device_object, irp);
 *			・
 *			・
 *			・
 *		complete_request(irp, irp->IoStatus.Status, irp->IoStatus.Information);
 */
NTSTATUS
call_driver(DEVICE_OBJECT* device_object, IRP* irp)
{
	NTSTATUS status;
	KEVENT event;

	/* KeWaitForSingleObject()にてブロックするため、PASSIVE_LEVEL必須です。 */
	ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);

	/* 完了を待つためのイベントを初期化します。 */
	KeInitializeEvent(&event, SynchronizationEvent, FALSE);

	/* 完了時に呼び出されるコールバック関数を登録します。 */
	IoSetCompletionRoutine(irp, call_driver_completion_routine, &event, TRUE, TRUE, TRUE);

	/* IOリクエストを発行し、完了を待ちます。
	 * * 独自の完了ルーチンを提供した場合は、IoCallDriver()の戻り値がSTATUS_PENDINGか否かにかかわらず、
	 *   必ず完了ルーチンが呼び出されるので、常にKeWaitForSingleObject()を行って構いません。(簡単のため)
	 */
	status = IoCallDriver(device_object, irp);
	//if(status == STATUS_PENDING) { /*←あってもなくてもOK*/
		KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
	//}                              /*←あってもなくてもOK*/

	/* * 通常は、IoCallDriver()を呼び出したら、もうIRPに触れてはいけません。
	 *   (下位ドライバが同期的にIoCompleteRequest()を呼び出し、もうIRPが破棄されているかも知れないから)
	 * * しかしここでは、独自の完了ルーチンからSTATUS_MORE_PROCESSING_REQUIREDを返しているので、
	 *   スタックロケーションの巻き戻しがそこで中断し、まだIRPが破棄されていないことが保証できます。
	 *   従って、以下のコードでIRPに触れても問題ありません。
	 */
	return irp->IoStatus.Status;
}

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

NTSTATUS
call_usbd(DEVICE_OBJECT* usbd, URB* urb)
{
	NTSTATUS status;
	KEVENT event;
	IRP* irp;
	IO_STACK_LOCATION* stack_location;
	IO_STATUS_BLOCK status_block;

	/* 完了を待つためのイベントを初期化します。 */
	KeInitializeEvent(&event, SynchronizationEvent, FALSE);

	/* 新しいIRPを作成します。
	 * I/Oマネージャが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) {
		return STATUS_UNSUCCESSFUL;
	}

	/* IRPのパラメータとして、URBを格納します。 */
	stack_location = IoGetNextIrpStackLocation(irp); /* IoGetCurrentIrpStackLocation()ではない!! */
	stack_location->Parameters.Others.Argument1 = urb;

	/* IOリクエストを発行し、必要に応じて完了を待ちます。
	 * * call_driver()と違って、下位ドライバがSTATUS_PENDINGを返した場合のみ、完了イベントを待ちます。
	 *   独自の完了ルーチンを提供していないので、あらゆる完了シナリオでI/Oマネージャがイベントをシグナル状態に
	 *   するかどうかについて、確信を持つことはできないからです。(『Microsoft WDM プログラミング』p.566より)
	 */
	status = IoCallDriver(usbd, irp);
	if(status == STATUS_PENDING) {
		KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
	}

	/* IRPは、I/Oマネージャによって、完了と同時にもう破棄されています。
	 * 結果は、IRPのIO_STATUS_BLOCKではなく、呼び出し側提供のIO_STATUS_BLOCKに格納されています。
	 */
	return status_block.Status; /* "return status"や"return irp->IoStatus.Status"ではない!! */
}

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

NTSTATUS
wait_for_terminate_thread(HANDLE thread_handle)
{
	NTSTATUS status;
	PKTHREAD thread_object;

	/* スレッドハンドルから、スレッドオブジェクトを取得します。
	 * * KeWaitForSingleObject()を使ってスレッドの終了を待つには、スレッドオブジェクトを指定する必要があります。
	 *   KeWaitForSingleObject()の引数に直接、スレッドハンドルを指定して、スレッドの終了を待つことはできません。
	 */
	status = ObReferenceObjectByHandle(
		thread_handle,
		THREAD_ALL_ACCESS,
		NULL,
		KernelMode,
		&thread_object,
		NULL);
	if(NT_SUCCESS(status)) {

		/* スレッドの終了を待ちます。 */
		status = KeWaitForSingleObject(thread_object, Executive, KernelMode, FALSE, NULL);

		/* 上で取得したスレッドオブジェクトの、参照を開放します。
		 * * スレッドハンドルももう不要ならば、呼び出し側にてZwClose()を使って開放してください。
		 */
		ObDereferenceObject(thread_object);
	}

	return status;
}

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

// [使用例]
//
//	NTSTATUS status;
//	HANDLE key_handle;
//	KEY_VALUE_FULL_INFORMATION* key_value_information;
//	UNICODE_STRING data;
//
//	status = IoOpenDeviceRegistryKey(
//		device_extension->physical_device_object,
//		PLUGPLAY_REGKEY_DEVICE,
//		STANDARD_RIGHTS_ALL,
//		&key_handle);
//	if(NT_SUCCESS(status)) {
//		key_value_information = query_value_key(key_handle, L"PortName");
//		if(key_value_information) {
//			if(key_value_information->Type == REG_SZ) {
//				RtlInitUnicodeString(&data, (PCWSTR)((int)key_value_information + key_value_information->DataOffset));
//				/** use data **/
//			}
//			ExFreePool(key_value_information);
//		}
//		ZwClose(key_handle);
//	}
//
KEY_VALUE_FULL_INFORMATION*
query_value_key(HANDLE key_handle, PCWSTR value_name)
{
	NTSTATUS status;
	UNICODE_STRING _value_name;
	int length;
	KEY_VALUE_FULL_INFORMATION* key_value_information;

	RtlInitUnicodeString(&_value_name, value_name);

	length = 0;
	status = ZwQueryValueKey(
		key_handle,
		&_value_name,
		KeyValueFullInformation,
		NULL,
		length,
		&length);
	//if(status != STATUS_BUFFER_TOO_SMALL) {
	//	return NULL;
	//}
	//↓length=0を指定した場合に、ZwQueryValueKey()がSTATUS_BUFFER_TOO_SMALL以外を返す場合があるそうです。
	//↓(『Microsoft WDM プログラミング』p.130より。手元のWin2K SP4では、STATUS_BUFFER_TOO_SMALLが返ってますが…)
	//↓ZwQueryValueKey()は失敗時にlengthを変更しないので、lengthが変更されているか否かで判断することにしました。
	if(!length) {
		return NULL;
	}

	key_value_information = ExAllocatePool(NonPagedPool, length);
	if(!key_value_information) {
		return NULL;
	}

	status = ZwQueryValueKey(
		key_handle,
		&_value_name,
		KeyValueFullInformation,
		key_value_information,
		length,
		&length);
	if(!NT_SUCCESS(status)) {
		ExFreePool(key_value_information);
		return NULL;
	}

	return key_value_information;
}

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

NTSTATUS
set_value_key(HANDLE key_handle, PCWSTR value_name, int type, const void* data, int data_size)
{
	NTSTATUS status;
	UNICODE_STRING _value_name;

	RtlInitUnicodeString(&_value_name, value_name);

	status = ZwSetValueKey(
		key_handle,
		&_value_name,
		0,
		type,
		(void*)data,
		data_size);

	return status;
}

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

PKTHREAD
create_thread(void (*start_routine)(void*), void* start_context)
{
	HANDLE thread_handle = NULL;
	PKTHREAD thread_object = NULL;
	//
	NTSTATUS status;

	/* スレッドを作成し、スレッドハンドルを取得します。
	 * * PsCreateSystemThread()が成功した場合、スレッドハンドルがスレッド実体への参照を保持しています。
	 */
	status = PsCreateSystemThread(
		&thread_handle,		/* ThreadHandle */
		THREAD_ALL_ACCESS,	/* DesiredAccess */
		NULL,			/* ObjectAttributes */
		NULL,			/* ProcessHandle */
		NULL,			/* ClientId */
		start_routine,		/* StartRoutine */
		start_context);		/* StartContext */
	if(!NT_SUCCESS(status)) {
		KdPrint(("PsCreateSystemThread failed\n"));
		goto L_EXIT;
	}

	/* スレッドハンドルから、スレッドオブジェクトを取得します。
	 * * ObReferenceObjectByHandle()が成功した場合、スレッドハンドルとスレッドオブジェクトがスレッド実体への参照を保持しています。
	 */
	status = ObReferenceObjectByHandle(
		thread_handle,		/* Handle */
		THREAD_ALL_ACCESS,	/* DesiredAccess */
		NULL,			/* ObjectType */
		KernelMode,		/* AccessMode */
		(void*)&thread_object,	/* Object */
		NULL);			/* HandleInformation */
	if(!NT_SUCCESS(status)) {
		KdPrint(("ObReferenceObjectByHandle failed\n"));
		goto L_EXIT;
	}

L_EXIT:

	/* PsCreateSystemThread()が成功していたら、ObReferenceObjectByHandle()の成否にかかわらず、スレッドハンドルを開放します。
	 * * ObReferenceObjectByHandle()が成功していた場合、スレッドオブジェクトがスレッド実体への参照を保持しています。
	 *   これが、通常の動作です。
	 * * ObReferenceObjectByHandle()が失敗していた場合、スレッド実体への参照が無く、スレッドは宙に浮いた状態になっています。
	 *   せめて、スレッドを強制終了させたいところですが、カーネルモードにはスレッドを強制終了するAPIが無いため、不可能です。
	 *   スレッドが動作したまま、ドライバがアンロードされると、おそらくメモリ保護エラーでシステムが停止するでしょう。
	 *   実際には、ObReferenceObjectByHandle()が失敗することはまず無いはずなので、このような問題は発生しないかも知れません。
	 *   しかし、もしもObReferenceObjectByHandle()が失敗した場合、上述の問題が発生することを留意しておいてください。(未解決)
	 */
	if(thread_handle) {
		ZwClose(thread_handle);
	}

	return thread_object;
}

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

void
delete_thread(PKTHREAD thread_object)
{
	NTSTATUS status;

	/* 確実にスレッドが停止するのを待ちます。
	 * あらかじめ呼び出し側にて、スレッドへの停止要求を発行しておいてください。
	 */
	status = KeWaitForSingleObject(thread_object, Executive, KernelMode, FALSE, NULL);
	if(!NT_SUCCESS(status)) {
		KdPrint(("KeWaitForSingleObject failed\n"));
		/* 強行(^^; */
	}

	/* スレッドオブジェクトを開放します。 */
	ObDereferenceObject(thread_object);
}

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

typedef struct _SERIALCOMM {
	UNICODE_STRING device_name;
	UNICODE_STRING port_name;
	UNICODE_STRING symbolic_link_name;
	//
	wchar_t _device_name[64];
	wchar_t _port_name[64];
	wchar_t _symbolic_link_name[64];
} SERIALCOMM;

/* COMポートを登録します。
 */
void*
register_serialcomm(DEVICE_OBJECT* physical_device_object, const wchar_t* device_name)
{
	SERIALCOMM* serialcomm = NULL;
	//
	HANDLE key_handle = NULL;
	KEY_VALUE_FULL_INFORMATION* key_value_information = NULL;
	//
	NTSTATUS status;

	KdPrint(("register_serialcomm\n"));

	/* SERIALCOMM構造体を確保し、初期化します。 */
	serialcomm = ExAllocatePool(NonPagedPool, sizeof(SERIALCOMM));
	if(!serialcomm) {
		KdPrint(("ExAllocatePool failed\n"));
		return NULL;
	}
	memset(serialcomm, 0, sizeof(SERIALCOMM));
	RtlInitEmptyUnicodeString(&serialcomm->device_name,
		serialcomm->_device_name, sizeof serialcomm->_device_name);
	RtlInitEmptyUnicodeString(&serialcomm->port_name,
		serialcomm->_port_name, sizeof serialcomm->_port_name);
	RtlInitEmptyUnicodeString(&serialcomm->symbolic_link_name,
		serialcomm->_symbolic_link_name, sizeof serialcomm->_symbolic_link_name);

	/* デバイス名を格納します。 */
	status = RtlAppendUnicodeToString(&serialcomm->device_name, device_name);
	if(!NT_SUCCESS(status)) {
		KdPrint(("RtlAppendUnicodeToString failed\n"));
		goto L_EXIT;
	}
	KdPrint(("Device name = %wZ\n", &serialcomm->device_name));

	/* システムによって自動的に割り当てられたポート名を取得します。 */
	status = IoOpenDeviceRegistryKey(
		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;
	}
	status = RtlAppendUnicodeToString(&serialcomm->port_name,
		(wchar_t*)((int)key_value_information + key_value_information->DataOffset));
	if(!NT_SUCCESS(status)) {
		KdPrint(("RtlAppendUnicodeToString failed\n"));
		goto L_EXIT;
	}
	KdPrint(("Port name = %wZ\n", &serialcomm->port_name));

	/* シリアルポートデバイスへのシンボリックリンク名を作成します。 */
	status = RtlAppendUnicodeToString(&serialcomm->symbolic_link_name, L"\\??\\");
	if(!NT_SUCCESS(status)) {
		KdPrint(("RtlAppendUnicodeToString failed\n"));
		goto L_EXIT;
	}
	status = RtlAppendUnicodeStringToString(&serialcomm->symbolic_link_name, &serialcomm->port_name);
	if(!NT_SUCCESS(status)) {
		KdPrint(("RtlAppendUnicodeToString failed\n"));
		goto L_EXIT;
	}
	KdPrint(("Symbolic link name = %wZ\n", &serialcomm->symbolic_link_name));

	/* シンボリックリンクを作成します。 */
	status = IoCreateSymbolicLink(&serialcomm->symbolic_link_name, &serialcomm->device_name);
	if(!NT_SUCCESS(status)) {
		KdPrint(("IoCreateSymbolicLink failed\n"));
		goto L_EXIT;
	}

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

L_EXIT:

	if(!NT_SUCCESS(status)) {
		if(serialcomm) {
			ExFreePool(serialcomm);
			serialcomm = NULL; /* 忘れないで!! */
		}
	}
	if(key_value_information) {
		ExFreePool(key_value_information);
	}
	if(key_handle) {
		ZwClose(key_handle);
	}

	return serialcomm;
}

/* COMポートを登録解除します。
 */
void
unregister_serialcomm(void* _serialcomm)
{
	SERIALCOMM* serialcomm = _serialcomm;

	KdPrint(("unregister_serialcomm\n"));

	if(serialcomm) {

		KdPrint(("Device name = %wZ\n", &serialcomm->device_name));
		KdPrint(("Port name = %wZ\n", &serialcomm->port_name));
		KdPrint(("Symbolic link name = %wZ\n", &serialcomm->symbolic_link_name));

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

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

		/* SERIALCOMM構造体を解放します。 */
		ExFreePool(serialcomm);
	}
}

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

USB_CONFIGURATION_DESCRIPTOR*
get_configuration_descriptor(DEVICE_OBJECT* usbd, int i_configuration)
{
	NTSTATUS status;
	URB urb;
	int configuration_descriptor_size;
	USB_CONFIGURATION_DESCRIPTOR* configuration_descriptor;

	/* コンフィギュレーション、インターフェイス、エンドポイントディスクリプタ全体のサイズを取得するために、
	 * まず、コンフィギュレーションディスクリプタのみを取得します。
	 */
	configuration_descriptor_size = sizeof(USB_CONFIGURATION_DESCRIPTOR);
	/*{{同じコード*/
	configuration_descriptor = ExAllocatePool(NonPagedPool, configuration_descriptor_size);
	if(!configuration_descriptor) {
		KdPrint(("ExAllocatePool failed\n"));
		return NULL;
	}
	UsbBuildGetDescriptorRequest(
		&urb,						/* Urb */
		sizeof(struct _URB_CONTROL_DESCRIPTOR_REQUEST),	/* Length */
		USB_CONFIGURATION_DESCRIPTOR_TYPE,		/* DescriptorType */
		(unsigned char)i_configuration,			/* Index */
		0,						/* LanguageId */
		configuration_descriptor,			/* TransferBuffer */
		NULL,						/* TransferBufferMDL */
		configuration_descriptor_size,			/* TransferBufferLength */
		NULL);						/* Link */
	status = call_usbd(usbd, &urb);
	if(!NT_SUCCESS(status)) {
		KdPrint(("call_usbd failed\n"));
		ExFreePool(configuration_descriptor);
		return NULL;
	}
	/*}}同じコード*/
	configuration_descriptor_size = configuration_descriptor->wTotalLength;
	ExFreePool(configuration_descriptor);

	/* コンフィギュレーション、インターフェイス、エンドポイントディスクリプタ全体を取得します。 */
	/*{{同じコード*/
	configuration_descriptor = ExAllocatePool(NonPagedPool, configuration_descriptor_size);
	if(!configuration_descriptor) {
		KdPrint(("ExAllocatePool failed\n"));
		return NULL;
	}
	UsbBuildGetDescriptorRequest(
		&urb,						/* Urb */
		sizeof(struct _URB_CONTROL_DESCRIPTOR_REQUEST),	/* Length */
		USB_CONFIGURATION_DESCRIPTOR_TYPE,		/* DescriptorType */
		(unsigned char)i_configuration,			/* Index */
		0,						/* LanguageId */
		configuration_descriptor,			/* TransferBuffer */
		NULL,						/* TransferBufferMDL */
		configuration_descriptor_size,			/* TransferBufferLength */
		NULL);						/* Link */
	status = call_usbd(usbd, &urb);
	if(!NT_SUCCESS(status)) {
		KdPrint(("call_usbd failed\n"));
		ExFreePool(configuration_descriptor);
		return NULL;
	}
	/*}}同じコード*/

	return configuration_descriptor;
}

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

/* コンフィギュレーションを選択します。
 */
int
set_configuration(DEVICE_OBJECT* usbd, USBD_PIPE_INFORMATION pipes[/*max_pipes*/], int max_pipes)
{
	int num_pipes = 0;
	//
	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(("set_configuration\n"));

	/* コンフィギュレーションディスクリプタを取得します。 */
	configuration_descriptor = get_configuration_descriptor(usbd, 0);
	if(!configuration_descriptor) {
		KdPrint(("get_configuration_descriptor failed\n"));
		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"));
		goto L_EXIT;
	}
	urb = USBD_CreateConfigurationRequestEx(configuration_descriptor, interface_list);
	if(!urb) {
		KdPrint(("USBD_CreateConfigurationRequestEx failed\n"));
		goto L_EXIT;
	}

	/* SET_CONFIGURATIONを発行します。 */
	status = call_usbd(usbd, urb);
	if(!NT_SUCCESS(status)) {
		KdPrint(("call_usbd failed\n"));
		goto L_EXIT;
	}
	interface_information = interface_list[0].Interface;

	/* バルクIN/OUTパイプを取得します。 */
	if((int)interface_information->NumberOfPipes > max_pipes) {
		KdPrint(("Number of pipes error\n"));
		goto L_EXIT;
	}
	while(num_pipes < (int)interface_information->NumberOfPipes) {
		pipes[num_pipes] = interface_information->Pipes[num_pipes];
		num_pipes++;
	}

L_EXIT:

	if(urb) {
		ExFreePool(urb);
	}
	if(configuration_descriptor) {
		ExFreePool(configuration_descriptor);
	}

	return num_pipes;
}

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

/* コンフィギュレーションを未選択に戻します。
 */
void
unset_configuration(DEVICE_OBJECT* usbd)
{
	URB urb;

	UsbBuildSelectConfigurationRequest(
		&urb,						/* Urb */
		sizeof(struct _URB_SELECT_CONFIGURATION),	/* Length */
		NULL);						/* ConfigurationDescriptor */
	call_usbd(usbd, &urb);
}

