/*	
 *	ioctl.c
 *
 *	PceCom - P/ECE USBシリアルポートデバイスドライバ
 *	Copyright (C) 2005 Naoyuki Sawa
 *
 *	* Sat Jul 23 17:10:00 JST 2005 Naoyuki Sawa
 *	- 1st リリース。
 */
#include "app.h"

#define VERBOSE

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

static void wait_on_mask_cancel_routine(DEVICE_OBJECT* device_object, IRP* irp);

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

NTSTATUS
init_ioctl(DEVICE_OBJECT* device_object)
{
	DEVICE_EXTENSION* device_extension = device_object->DeviceExtension;
	IOCTL* ioctl = &device_extension->ioctl;

	KdPrint(("init_ioctl\n"));

	/** no job **/

	return STATUS_SUCCESS;
}

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

void
exit_ioctl(DEVICE_OBJECT* device_object)
{
	DEVICE_EXTENSION* device_extension = device_object->DeviceExtension;
	IOCTL* ioctl = &device_extension->ioctl;

	KdPrint(("exit_ioctl\n"));

	/** no job **/
}

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

NTSTATUS
ioctl_serial_set_queue_size(IOCTL* ioctl, IRP* irp, SERIAL_QUEUE_SIZE* queue_size)
{
	DEVICE_EXTENSION* device_extension = CONTAINING_RECORD(ioctl, DEVICE_EXTENSION, ioctl);
	RDWR* rdwr = &device_extension->rdwr;
	//
	void* new_buffer;

	KdPrint(("InSize  = %d\n", queue_size->InSize ));
	KdPrint(("OutSize = %d\n", queue_size->OutSize));

ENTER_CS;

	ioctl->queue_size = *queue_size;

	/* * 指定された受信バッファサイズが現在のサイズよりも大きければ、受信バッファを拡張します。
	 *   現在のサイズよりも小さければ、何もしません。Serial IOCTLの仕様です。
	 * - 2005/07/22現在のpolling_thread_routine()の実装は、バッファサイズが減らない仕様に依存しています。
	 *   もしも誤ってバッファサイズを減らしてしまうと、不正なメモリアクセスとなるので、注意してください。
	 */
	if((int)queue_size->InSize > rdwr->read_capacity) {
		new_buffer = ExAllocatePool(NonPagedPool, queue_size->InSize); /* DISPATCH_LEVELでも、NonPagedPoolからの確保は可能 */
		if(!new_buffer) {
			KdPrint(("ExAllocatePool failed\n"));
			return STATUS_UNSUCCESSFUL;
		}
		memcpy(new_buffer, rdwr->read_buffer, rdwr->read_length);
		ExFreePool(rdwr->read_buffer);
		rdwr->read_buffer = new_buffer;
		rdwr->read_capacity = queue_size->InSize; /*{{2005/07/30 Naoyuki Sawa 追加 : 新しいサイズの格納を忘れていました。}}*/
	}

	/* * 指定された送信バッファサイズが現在のサイズよりも大きければ、送信バッファを拡張します。
	 *   現在のサイズよりも小さければ、何もしません。Serial IOCTLの仕様です。
	 * - 2005/07/22現在のpolling_thread_routine()の実装は、バッファサイズが減らない仕様に依存しています。
	 *   もしも誤ってバッファサイズを減らしてしまうと、不正なメモリアクセスとなるので、注意してください。
	 */
	if((int)queue_size->OutSize > rdwr->write_capacity) {
		new_buffer = ExAllocatePool(NonPagedPool, queue_size->OutSize); /* DISPATCH_LEVELでも、NonPagedPoolからの確保は可能 */
		if(!new_buffer) {
			KdPrint(("ExAllocatePool failed\n"));
			return STATUS_UNSUCCESSFUL;
		}
		memcpy(new_buffer, rdwr->write_buffer, rdwr->write_length);
		ExFreePool(rdwr->write_buffer);
		rdwr->write_buffer = new_buffer;
		rdwr->write_capacity = queue_size->OutSize; /*{{2005/07/30 Naoyuki Sawa 追加 : 新しいサイズの格納を忘れていました。}}*/
	}

LEAVE_CS;

	return STATUS_SUCCESS;
}

NTSTATUS
ioctl_serial_get_commstatus(IOCTL* ioctl, IRP* irp, SERIAL_STATUS* comm_status)
{
	DEVICE_EXTENSION* device_extension = CONTAINING_RECORD(ioctl, DEVICE_EXTENSION, ioctl);
	RDWR* rdwr = &device_extension->rdwr;
ENTER_CS;
	*comm_status = ioctl->comm_status;
	comm_status->AmountInInQueue = rdwr->read_length;
	comm_status->AmountInOutQueue = rdwr->write_length;
LEAVE_CS;
	return STATUS_SUCCESS;
}

NTSTATUS
ioctl_serial_get_modemstatus(IOCTL* ioctl, IRP* irp, int* modem_status)
{
ENTER_CS;
	*modem_status = ioctl->modem_status;
LEAVE_CS;
	return STATUS_SUCCESS;
}

NTSTATUS
ioctl_serial_set_dtr(IOCTL* ioctl, IRP* irp, void* junk)
{
ENTER_CS;
	ioctl->dtr_rts |= SERIAL_DTR_STATE;
LEAVE_CS;
	return STATUS_SUCCESS;
}

NTSTATUS
ioctl_serial_clr_dtr(IOCTL* ioctl, IRP* irp, void* junk)
{
ENTER_CS;
	ioctl->dtr_rts &= ~SERIAL_DTR_STATE;
LEAVE_CS;
	return STATUS_SUCCESS;
}

NTSTATUS
ioctl_serial_set_rts(IOCTL* ioctl, IRP* irp, void* junk)
{
ENTER_CS;
	ioctl->dtr_rts |= SERIAL_RTS_STATE;
LEAVE_CS;
	return STATUS_SUCCESS;
}

NTSTATUS
ioctl_serial_clr_rts(IOCTL* ioctl, IRP* irp, void* junk)
{
ENTER_CS;
	ioctl->dtr_rts &= ~SERIAL_RTS_STATE;
LEAVE_CS;
	return STATUS_SUCCESS;
}

NTSTATUS
ioctl_serial_get_dtrrts(IOCTL* ioctl, IRP* irp, int* dtr_rts)
{
ENTER_CS;
	*dtr_rts = ioctl->dtr_rts;
LEAVE_CS;
	return STATUS_SUCCESS;
}

NTSTATUS
ioctl_serial_purge(IOCTL* ioctl, IRP* irp, int* purge_mask)
{
	DEVICE_EXTENSION* device_extension = CONTAINING_RECORD(ioctl, DEVICE_EXTENSION, ioctl);
	DEVICE_OBJECT* device_object = device_extension->device_object;
	RDWR* rdwr = &device_extension->rdwr;

	/* 受信バッファクリア。 */
	if(*purge_mask & SERIAL_PURGE_RXCLEAR) {
		rdwr->read_length = 0; /* 1word書き込みなので、排他制御不要 */
	}

	/* 受信IRPキャンセル。 */
	if(*purge_mask & SERIAL_PURGE_RXABORT) {
		abort_read(device_object);
	}

	/* 送信バッファクリア。 */
	if(*purge_mask & SERIAL_PURGE_TXCLEAR) {
		rdwr->write_length = 0; /* 1word書き込みなので、排他制御不要 */
	}

	/* 送信IRPキャンセル。 */
	if(*purge_mask & SERIAL_PURGE_TXABORT) {
		abort_write(device_object);
	}

	return STATUS_SUCCESS;
}

NTSTATUS
ioctl_serial_set_line_control(IOCTL* ioctl, IRP* irp, SERIAL_LINE_CONTROL* line_control)
{
ENTER_CS;
	ioctl->line_control = *line_control;
LEAVE_CS;
	return STATUS_SUCCESS;
}

NTSTATUS
ioctl_serial_get_line_control(IOCTL* ioctl, IRP* irp, SERIAL_LINE_CONTROL* line_control)
{
ENTER_CS;
	*line_control = ioctl->line_control;
LEAVE_CS;
	return STATUS_SUCCESS;
}

NTSTATUS
ioctl_serial_set_baud_rate(IOCTL* ioctl, IRP* irp, SERIAL_BAUD_RATE* baud_rate)
{
ENTER_CS;
	ioctl->baud_rate = *baud_rate;
LEAVE_CS;
	return STATUS_SUCCESS;
}

NTSTATUS
ioctl_serial_get_baud_rate(IOCTL* ioctl, IRP* irp, SERIAL_BAUD_RATE* baud_rate)
{
ENTER_CS;
	*baud_rate = ioctl->baud_rate;
LEAVE_CS;
	return STATUS_SUCCESS;
}

NTSTATUS
ioctl_serial_set_handflow(IOCTL* ioctl, IRP* irp, SERIAL_HANDFLOW* handflow)
{
ENTER_CS;
	ioctl->handflow = *handflow;
LEAVE_CS;
	return STATUS_SUCCESS;
}

NTSTATUS
ioctl_serial_get_handflow(IOCTL* ioctl, IRP* irp, SERIAL_HANDFLOW* handflow)
{
ENTER_CS;
	*handflow = ioctl->handflow;
LEAVE_CS;
	return STATUS_SUCCESS;
}

NTSTATUS
ioctl_serial_set_timeouts(IOCTL* ioctl, IRP* irp, SERIAL_TIMEOUTS* timeouts)
{
	KdPrint(("ReadIntervalTimeout         = %d\n", timeouts->ReadIntervalTimeout        ));
	KdPrint(("ReadTotalTimeoutMultiplier  = %d\n", timeouts->ReadTotalTimeoutMultiplier ));
	KdPrint(("ReadTotalTimeoutConstant    = %d\n", timeouts->ReadTotalTimeoutConstant   ));
	KdPrint(("WriteTotalTimeoutMultiplier = %d\n", timeouts->WriteTotalTimeoutMultiplier));
	KdPrint(("WriteTotalTimeoutConstant   = %d\n", timeouts->WriteTotalTimeoutConstant  ));
ENTER_CS;
	ioctl->timeouts = *timeouts;
LEAVE_CS;
	return STATUS_SUCCESS;
}

NTSTATUS
ioctl_serial_get_timeouts(IOCTL* ioctl, IRP* irp, SERIAL_TIMEOUTS* timeouts)
{
ENTER_CS;
	*timeouts = ioctl->timeouts;
LEAVE_CS;
	return STATUS_SUCCESS;
}

NTSTATUS
ioctl_serial_set_chars(IOCTL* ioctl, IRP* irp, SERIAL_CHARS* chars)
{
	KdPrint(("EofChar   = $%02x\n", chars->EofChar  ));
	KdPrint(("ErrorChar = $%02x\n", chars->ErrorChar));
	KdPrint(("BreakChar = $%02x\n", chars->BreakChar));
	KdPrint(("EventChar = $%02x\n", chars->EventChar));
	KdPrint(("XonChar   = $%02x\n", chars->XonChar  ));
	KdPrint(("XoffChar  = $%02x\n", chars->XoffChar ));
ENTER_CS;
	ioctl->chars = *chars;
LEAVE_CS;
	return STATUS_SUCCESS;
}

NTSTATUS
ioctl_serial_get_chars(IOCTL* ioctl, IRP* irp, SERIAL_CHARS* chars)
{
ENTER_CS;
	*chars = ioctl->chars;
LEAVE_CS;
	return STATUS_SUCCESS;
}

NTSTATUS
ioctl_serial_set_wait_mask(IOCTL* ioctl, IRP* irp, int* wait_mask)
{
	IRP* wait_on_mask_irp;

	if(*wait_mask & SERIAL_EV_RXCHAR)   KdPrint(("SERIAL_EV_RXCHAR   (Any Character received)\n"));
	if(*wait_mask & SERIAL_EV_RXFLAG)   KdPrint(("SERIAL_EV_RXFLAG   (Received certain character)\n"));
	if(*wait_mask & SERIAL_EV_TXEMPTY)  KdPrint(("SERIAL_EV_TXEMPTY  (Transmitt Queue Empty)\n"));
	if(*wait_mask & SERIAL_EV_CTS)      KdPrint(("SERIAL_EV_CTS      (CTS changed state)\n"));
	if(*wait_mask & SERIAL_EV_DSR)      KdPrint(("SERIAL_EV_DSR      (DSR changed state)\n"));
	if(*wait_mask & SERIAL_EV_RLSD)     KdPrint(("SERIAL_EV_RLSD     (RLSD changed state)\n"));
	if(*wait_mask & SERIAL_EV_BREAK)    KdPrint(("SERIAL_EV_BREAK    (BREAK received)\n"));
	if(*wait_mask & SERIAL_EV_ERR)      KdPrint(("SERIAL_EV_ERR      (Line status error occurred)\n"));
	if(*wait_mask & SERIAL_EV_RING)     KdPrint(("SERIAL_EV_RING     (Ring signal detected)\n"));
	if(*wait_mask & SERIAL_EV_PERR)     KdPrint(("SERIAL_EV_PERR     (Printer error occured)\n"));
	if(*wait_mask & SERIAL_EV_RX80FULL) KdPrint(("SERIAL_EV_RX80FULL (Receive buffer is 80 percent full)\n"));
	if(*wait_mask & SERIAL_EV_EVENT1)   KdPrint(("SERIAL_EV_EVENT1   (Provider specific event 1)\n"));
	if(*wait_mask & SERIAL_EV_EVENT2)   KdPrint(("SERIAL_EV_EVENT2   (Provider specific event 2)\n"));

ENTER_CS;

	/* SET_WAIT_MASKは、WAIT_ON_MASK IRPをInformation=0で正常終了させます。(仕様) */
	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); /* 保持しなくなったら、キャンセルルーチンを解除 */
	}

	ioctl->wait_mask = *wait_mask;

LEAVE_CS;

	if(wait_on_mask_irp) {
		complete_wait_on_mask(wait_on_mask_irp, 0);
	}

	return STATUS_SUCCESS;
}

NTSTATUS
ioctl_serial_get_wait_mask(IOCTL* ioctl, IRP* irp, int* wait_mask)
{
ENTER_CS;
	*wait_mask = ioctl->wait_mask;
LEAVE_CS;
	return STATUS_SUCCESS;
}

NTSTATUS
ioctl_serial_wait_on_mask(IOCTL* ioctl, IRP* irp, int* wait_mask)
{
	NTSTATUS status;
ENTER_CS;
	/* WAIT_ON_MASKの二重実行や、SET_WAIT_MASK=0の状態で実行すると、エラー扱いとなります。(仕様) */
	if(!ioctl->wait_on_mask_irp && ioctl->wait_mask) {
		IoMarkIrpPending(irp); /* 忘れないで!! */
		IoSetCancelRoutine(irp, wait_on_mask_cancel_routine); /* 保持している間、キャンセルルーチンを設定 */
		ioctl->wait_on_mask_irp = irp;
		status = STATUS_PENDING;
	} else {
		KdPrint(("Invalid parameter\n"));
		status = STATUS_INVALID_PARAMETER; /* 仕様 */
	}
LEAVE_CS;
	return status;
}

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

typedef struct _IOCTL_TABLE {
	int   code;
	void* ioctl;
	int   length; /* + = in(set), - = out(get) */
} IOCTL_TABLE;

IOCTL_TABLE ioctl_table[] = {
	{	IOCTL_SERIAL_SET_QUEUE_SIZE,	ioctl_serial_set_queue_size,	 (int)sizeof(SERIAL_QUEUE_SIZE)		},
//	{	IOCTL_SERIAL_GET_PROPERTIES,	ioctl_serial_get_properties,						},
	{	IOCTL_SERIAL_GET_COMMSTATUS,	ioctl_serial_get_commstatus,	-(int)sizeof(SERIAL_STATUS)		},
	{	IOCTL_SERIAL_GET_MODEMSTATUS,	ioctl_serial_get_modemstatus,	-(int)sizeof(int)			},
	{	IOCTL_SERIAL_SET_DTR,		ioctl_serial_set_dtr,		 (int)0					},
	{	IOCTL_SERIAL_CLR_DTR,		ioctl_serial_clr_dtr,		 (int)0					},
	{	IOCTL_SERIAL_SET_RTS,		ioctl_serial_set_rts,		 (int)0					},
	{	IOCTL_SERIAL_CLR_RTS,		ioctl_serial_clr_rts,		 (int)0					},
	{	IOCTL_SERIAL_GET_DTRRTS,	ioctl_serial_get_dtrrts,	-(int)sizeof(int)			},
//	{	IOCTL_SERIAL_SET_XON,		ioctl_serial_set_xon,							},
//	{	IOCTL_SERIAL_SET_XOFF,		ioctl_serial_set_xoff,							},
//	{	IOCTL_SERIAL_XOFF_COUNTER,	ioctl_serial_xoff_counter,						},
	{	IOCTL_SERIAL_PURGE,		ioctl_serial_purge,		 (int)sizeof(int)			},
//	{	IOCTL_SERIAL_RESET_DEVICE,	ioctl_serial_reset_device,						},
//	{	IOCTL_SERIAL_SET_BREAK_ON,	ioctl_serial_set_break_on,						},
//	{	IOCTL_SERIAL_SET_BREAK_OFF,	ioctl_serial_set_break_off,						},
	{	IOCTL_SERIAL_SET_LINE_CONTROL,	ioctl_serial_set_line_control,	 (int)sizeof(SERIAL_LINE_CONTROL)	},
	{	IOCTL_SERIAL_GET_LINE_CONTROL,	ioctl_serial_get_line_control,	-(int)sizeof(SERIAL_LINE_CONTROL)	},
	{	IOCTL_SERIAL_SET_BAUD_RATE,	ioctl_serial_set_baud_rate,	 (int)sizeof(SERIAL_BAUD_RATE)		},
	{	IOCTL_SERIAL_GET_BAUD_RATE,	ioctl_serial_get_baud_rate,	-(int)sizeof(SERIAL_BAUD_RATE)		},
	{	IOCTL_SERIAL_SET_HANDFLOW,	ioctl_serial_set_handflow,	 (int)sizeof(SERIAL_HANDFLOW)		},
	{	IOCTL_SERIAL_GET_HANDFLOW,	ioctl_serial_get_handflow,	-(int)sizeof(SERIAL_HANDFLOW)		},
	{	IOCTL_SERIAL_SET_TIMEOUTS,	ioctl_serial_set_timeouts,	 (int)sizeof(SERIAL_TIMEOUTS)		},
	{	IOCTL_SERIAL_GET_TIMEOUTS,	ioctl_serial_get_timeouts,	-(int)sizeof(SERIAL_TIMEOUTS)		},
	{	IOCTL_SERIAL_SET_CHARS,		ioctl_serial_set_chars,		 (int)sizeof(SERIAL_CHARS)		},
	{	IOCTL_SERIAL_GET_CHARS,		ioctl_serial_get_chars,		-(int)sizeof(SERIAL_CHARS)		},
//	{	IOCTL_SERIAL_IMMEDIATE_CHAR,	ioctl_serial_immediate_char,						},
	{	IOCTL_SERIAL_SET_WAIT_MASK,	ioctl_serial_set_wait_mask,	 (int)sizeof(int)			},
	{	IOCTL_SERIAL_GET_WAIT_MASK,	ioctl_serial_get_wait_mask,	-(int)sizeof(int)			},
	{	IOCTL_SERIAL_WAIT_ON_MASK,	ioctl_serial_wait_on_mask,	-(int)sizeof(int)			},
};

NTSTATUS
dispatch_ioctl(DEVICE_OBJECT* device_object, IRP* irp)
{
	DEVICE_EXTENSION* device_extension = device_object->DeviceExtension;
	IO_STACK_LOCATION* stack_location = IoGetCurrentIrpStackLocation(irp);
	//
	void* system_buffer      = irp->AssociatedIrp.SystemBuffer;
	int ioctl_code           = stack_location->Parameters.DeviceIoControl.IoControlCode;
	int input_buffer_length  = stack_location->Parameters.DeviceIoControl.InputBufferLength;
	int output_buffer_length = stack_location->Parameters.DeviceIoControl.OutputBufferLength;
	//
	int i;
	int length;
	NTSTATUS status;

#ifdef VERBOSE
	KdPrint(("dispatch_ioctl - %s\n", serial_ioctl_name(ioctl_code)));
#endif /*QUIET*/

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

	/* IOCTLコードに一致するエントリを検索します。 */
	for(i = 0; i < sizeof ioctl_table / sizeof *ioctl_table; i++) {
		if(ioctl_table[i].code == ioctl_code) {

			/* 入出力を伴うIOCTLならば、バッファサイズを検査します。 */
			length = ioctl_table[i].length;
			if(length < 0) { /* out */
				length = -length; /* 出力IOCTLは、出力バイト数を返す */
				if(output_buffer_length < length) {
					KdPrint(("Buffer too small"));
					IoReleaseRemoveLock(&device_extension->remove_lock, irp);
					return complete_request(irp, STATUS_BUFFER_TOO_SMALL, 0);
				}
			} else if(length > 0) { /* in */
				if(input_buffer_length < length) {
					KdPrint(("Buffer too small"));
					IoReleaseRemoveLock(&device_extension->remove_lock, irp);
					return complete_request(irp, STATUS_BUFFER_TOO_SMALL, 0);
				}
				length = 0; /* 入力IOCTLは、0を返す */
			}

			/* IOCTLを実行します。 */
			status = ((NTSTATUS (*)(IOCTL*, IRP*, void*))ioctl_table[i].ioctl)(&device_extension->ioctl, irp, system_buffer);
			if(status != STATUS_PENDING) {
				if(!NT_SUCCESS(status)) {
					length = 0; /* 失敗ならば、0を返す */
				}
				IoReleaseRemoveLock(&device_extension->remove_lock, irp);
				complete_request(irp, status, length);
			}
			return status;
		}
	}

	KdPrint(("Invalid parameter\n"));
	IoReleaseRemoveLock(&device_extension->remove_lock, irp);
	return complete_request(irp, STATUS_INVALID_PARAMETER, 0); /* 仕様 */
}

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

void
complete_wait_on_mask(IRP* irp, int wait_mask)
{
	IO_STACK_LOCATION* stack_location = IoGetCurrentIrpStackLocation(irp);
	DEVICE_OBJECT* device_object = stack_location->DeviceObject;
	DEVICE_EXTENSION* device_extension = device_object->DeviceExtension;

#ifdef VERBOSE
	KdPrint(("complete_wait_on_mask\n"));
#endif /*VERBOSE*/

	*(int*)irp->AssociatedIrp.SystemBuffer = wait_mask;

	IoReleaseRemoveLock(&device_extension->remove_lock, irp);
	complete_request(irp, STATUS_SUCCESS, sizeof(int));
}

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

/* rdwr.c - rdwr_cancel_routine()のコメントを参照してください。
 */
static void
wait_on_mask_cancel_routine(DEVICE_OBJECT* device_object, IRP* irp)
{
	DEVICE_EXTENSION* device_extension = device_object->DeviceExtension;
	IOCTL* ioctl = &device_extension->ioctl;

	ASSERT(ioctl->wait_on_mask_irp == irp);
	ioctl->wait_on_mask_irp = NULL;

	IoReleaseCancelSpinLock(irp->CancelIrql);

	IoReleaseRemoveLock(&device_extension->remove_lock, irp);
	complete_request(irp, STATUS_CANCELLED, 0);
}
