/*
 * CSQ Test Driver
 * Copyright (c) 2004, Vizzini (vizzini@plasmic.com)
 * Released under the GNU GPL for the ReactOS project
 *
 * This driver is designed to exercise the cancel-safe IRP queue logic.
 * Please refer to reactos/include/ddk/csq.h and reactos/drivers/lib/csq.
 */
#include <ntddk.h>
#include <csq.h>

/* XXX shortcomings in our headers... */
#define assert(x)
#ifndef KdPrint
#define KdPrint(x) DbgPrint x
#endif

/* Device name */
#define NT_DEVICE_NAME L"\\Device\\csqtest"

/* DosDevices name */
#define DOS_DEVICE_NAME L"\\??\\csqtest"

/* Global CSQ struct that the CSQ functions init */
IO_CSQ Csq;

/* List and lock for the actual IRP queue */
LIST_ENTRY IrpQueue;
KSPIN_LOCK IrpQueueLock;

/* Device object */
PDEVICE_OBJECT DeviceObject;

/*
 * CSQ Callbacks
 */
VOID NTAPI CsqInsertIrp(PIO_CSQ Csq, PIRP Irp)
{
	KdPrint(("Inserting IRP 0x%x into CSQ\n", Irp));
	InsertTailList(&IrpQueue, &Irp->Tail.Overlay.ListEntry);
}

VOID NTAPI CsqRemoveIrp(PIO_CSQ Csq, PIRP Irp)
{
	KdPrint(("Removing IRP 0x%x from CSQ\n", Irp));
	RemoveEntryList(&Irp->Tail.Overlay.ListEntry);
}

PIRP NTAPI CsqPeekNextIrp(PIO_CSQ Csq, PIRP Irp, PVOID PeekContext)
{
	KdPrint(("Peeking for next IRP\n"));

	if(Irp)
		return CONTAINING_RECORD(&Irp->Tail.Overlay.ListEntry.Flink, IRP, Tail.Overlay.ListEntry);

	if(IsListEmpty(&IrpQueue))
		return NULL;

	return CONTAINING_RECORD(IrpQueue.Flink, IRP, Tail.Overlay.ListEntry);
}

VOID NTAPI CsqAcquireLock(PIO_CSQ Csq, PKIRQL Irql)
{
	KdPrint(("Acquiring spin lock\n"));
	KeAcquireSpinLock(&IrpQueueLock, Irql);
}

VOID NTAPI CsqReleaseLock(PIO_CSQ Csq, KIRQL Irql)
{
	KdPrint(("Releasing spin lock\n"));
	KeReleaseSpinLock(&IrpQueueLock, Irql);
}

VOID NTAPI CsqCompleteCancelledIrp(PIO_CSQ Csq, PIRP Irp)
{
	KdPrint(("cancelling irp 0x%x\n", Irp));
	Irp->IoStatus.Status = STATUS_CANCELLED;
	Irp->IoStatus.Information = 0;
	IoCompleteRequest(Irp, IO_NO_INCREMENT);
}

NTSTATUS NTAPI CsqInsertIrpEx(PIO_CSQ Csq, PIRP Irp, PVOID InsertContext)
/*
 * FUNCTION: Insert into IRP queue, with extra context
 *
 * NOTE: Switch call in DriverEntry to IoCsqInitializeEx to use this
 */
{
	CsqInsertIrp(Csq, Irp);
	return STATUS_PENDING;
}

/*
 * DISPATCH ROUTINES
 */

NTSTATUS NTAPI DispatchCreateCloseCleanup(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
	PIO_STACK_LOCATION StackLocation = IoGetCurrentIrpStackLocation(Irp);

	if(StackLocation->MajorFunction == IRP_MJ_CLEANUP)
		{
			/* flush the irp queue */
			PIRP CurrentIrp;

			KdPrint(("csqtest: Cleanup received; flushing the IRP queue with cancel\n"));

			while((CurrentIrp = IoCsqRemoveNextIrp(&Csq, 0)))
				{
					CurrentIrp->IoStatus.Status = STATUS_CANCELLED;
					CurrentIrp->IoStatus.Information = 0;

					IoCompleteRequest(CurrentIrp, IO_NO_INCREMENT);
				}
		}

	Irp->IoStatus.Status = STATUS_SUCCESS;
	Irp->IoStatus.Information = 0;

	IoCompleteRequest(Irp, IO_NO_INCREMENT);

	return STATUS_SUCCESS;
}

NTSTATUS NTAPI DispatchReadWrite(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
	/* According to the cancel sample in the DDK, IoCsqInsertIrp() marks the irp pending */
	/* However, I think it's wrong. */
	IoMarkIrpPending(Irp);
	IoCsqInsertIrp(&Csq, Irp, 0);

	return STATUS_PENDING;
}

NTSTATUS NTAPI DispatchIoctl(PDEVICE_OBJECT DeviceObject, PIRP Irp)
/*
 * all IOCTL requests flush the irp queue
 */
{
	PIRP CurrentIrp;

	KdPrint(("csqtest: Ioctl received; flushing the IRP queue with success\n"));

	while((CurrentIrp = IoCsqRemoveNextIrp(&Csq, 0)))
		{
			CurrentIrp->IoStatus.Status = STATUS_SUCCESS;
			CurrentIrp->IoStatus.Information = 0;

			IoCompleteRequest(CurrentIrp, IO_NO_INCREMENT);
		}

	Irp->IoStatus.Status = STATUS_SUCCESS;
	Irp->IoStatus.Information = 0;

	IoCompleteRequest(Irp, IO_NO_INCREMENT);

	return STATUS_SUCCESS;
}

VOID NTAPI Unload(PDRIVER_OBJECT DriverObject)
/*
 * Function: called by the OS to release resources before unload
 */
{
	UNICODE_STRING LinkName;

	RtlInitUnicodeString(&LinkName, DOS_DEVICE_NAME);

	IoDeleteSymbolicLink(&LinkName);

	if(DeviceObject)
		IoDeleteDevice(DeviceObject);
}

/*
 * DriverEntry
 */

NTSTATUS NTAPI DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
	NTSTATUS Status;
	UNICODE_STRING NtName;
	UNICODE_STRING DosName;

	DriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreateCloseCleanup;
	DriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchCreateCloseCleanup;
	DriverObject->MajorFunction[IRP_MJ_CLEANUP] = DispatchCreateCloseCleanup;
	DriverObject->MajorFunction[IRP_MJ_READ] = DispatchReadWrite;
	DriverObject->MajorFunction[IRP_MJ_WRITE] = DispatchReadWrite;
	DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchIoctl;
	DriverObject->DriverUnload = Unload;

	Status = IoCsqInitialize(&Csq, CsqInsertIrp, CsqRemoveIrp, CsqPeekNextIrp,
													 CsqAcquireLock, CsqReleaseLock, CsqCompleteCancelledIrp);

	if(Status != STATUS_SUCCESS)
		KdPrint(("csqtest: IoCsqInitialize failed: 0x%x\n", Status));
	else
		KdPrint(("csqtest: IoCsqInitialize succeeded\n"));

	InitializeListHead(&IrpQueue);
	KeInitializeSpinLock(&IrpQueueLock);

	/* Set up a device */
	RtlInitUnicodeString(&NtName, NT_DEVICE_NAME);
	Status = IoCreateDevice(DriverObject, 0, &NtName, FILE_DEVICE_UNKNOWN, 0, 0, &DeviceObject);

	if(!NT_SUCCESS(Status))
		{
			KdPrint(("csqtest: Unable to create device: 0x%x\n", Status));
			return Status;
		}

	RtlInitUnicodeString(&DosName, DOS_DEVICE_NAME);
	Status = IoCreateSymbolicLink(&DosName, &NtName);

	if(!NT_SUCCESS(Status))
		{
			KdPrint(("csqtest: Unable to create link: 0x%x\n", Status));
			return Status;
		}

	DeviceObject->Flags |= DO_BUFFERED_IO;

	return STATUS_SUCCESS;
}