// $Id: dabusb.c,v 1.40 2000/01/08 09:00:11 fliegl Exp $
//
// Include files needed for WDM driver support
//
#include <wdm.h>
#include "stdarg.h"
#include "stdio.h"

//
// Include files needed for USB support
//
#include "usbdi.h"
#include "usbdlib.h"

//
// Include file for the dabusb Device
//
#include "bcusb.h"

#if 0
#define VLOG(x)  do { x; } while (0)
#else
#define VLOG(x)  do {  } while (0)
#endif

#if 1
#define ELOG(x)  do { x; } while (0)
#else
#define ELOG(x)  do {  } while (0)
#endif

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

#ifdef CONNECTEVENT
/* IoCreateSynchronizationObject does not exist in W98 WDM */
static PKEVENT sync_event;
static HANDLE sync_event_handle;
#endif

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

LONG dabusb_decrement_io_count(PDEVICE_OBJECT fdo)
{
	pdabusb_t s = fdo->DeviceExtension;
	LONG ioCount;
	
	ioCount = InterlockedDecrement(&s->pending_io_count);
	if (ioCount <= 1) {
		/* trigger no pending io */
		KeSetEvent(&s->NoPendingIoEvent, 1, FALSE);
	}
	if (ioCount == 0) {
		/* trigger remove-device event */
		KeSetEvent(&s->RemoveEvent, 1, FALSE);
	}
	return ioCount;
}

VOID dabusb_increment_io_count(PDEVICE_OBJECT fdo)
{
	pdabusb_t s = fdo->DeviceExtension;
        LONG ioCount;

	KeClearEvent(&s->RemoveEvent);
	KeClearEvent(&s->NoPendingIoEvent);
	ioCount = InterlockedIncrement(&s->pending_io_count);
	if (ioCount <= 1) {
		/* trigger no pending io */
		KeSetEvent(&s->NoPendingIoEvent, 1, FALSE);
	}
}

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

NTSTATUS OnRequestComplete(IN PDEVICE_OBJECT fdo, IN PIRP irp, IN PKEVENT pev )
{
	KeSetEvent(pev, 0, FALSE);
	return STATUS_MORE_PROCESSING_REQUIRED;
}

NTSTATUS ForwardAndWait(IN PDEVICE_OBJECT fdo, IN PIRP irp )
{
	KEVENT event;
	pdabusb_t s = (pdabusb_t) fdo->DeviceExtension;
	NTSTATUS ret;

        ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);

	//
	// Initialize a kernel event object to use in waiting for the lower-level
	// driver to finish processing the object. 
	//
	KeInitializeEvent(&event, NotificationEvent, FALSE);

	IoCopyCurrentIrpStackLocationToNext(irp);
	IoSetCompletionRoutine(irp, (PIO_COMPLETION_ROUTINE) OnRequestComplete, (PVOID) &event, TRUE, TRUE, TRUE);

	ret = IoCallDriver(s->sdo, irp);

	if (ret == STATUS_PENDING) {
		KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
		ret = irp->IoStatus.Status;
	}

	return ret;
}

// -----------------------------------------------------------

static NTSTATUS dabusb_StartDevice(PDEVICE_OBJECT fdo)
{
	NTSTATUS ret = STATUS_SUCCESS;
        unsigned char mode;
        
	VLOG(printk(MODSTR "enter dabusb_StartDevice\n"));

        //usb_control_msg(fdo, 0xc0, 0xc8, 0, 0, 1, &mode, 500);

	VLOG(printk(MODSTR "exit dabusb_StartDevice (%x)\n", ret));

	return ret;
}

NTSTATUS dabusb_RemoveDevice( IN  PDEVICE_OBJECT fdo)
{
	pdabusb_t s = fdo->DeviceExtension;
	NTSTATUS ret = STATUS_SUCCESS;

	VLOG(printk(MODSTR "enter dabusb_RemoveDevice\n"));

	async_cancel_all(fdo);
	abort_pipes(fdo);

	IoDeleteSymbolicLink(&s->deviceLinkUnicodeString);
	RtlFreeUnicodeString(&s->deviceLinkUnicodeString);

	IoDetachDevice(s->sdo);
	IoDeleteDevice (fdo);

	VLOG(printk(MODSTR "exit dabusb_RemoveDevice (%x)\n", ret));

	return ret;
}

NTSTATUS dabusb_HandleStartDevice( IN PDEVICE_OBJECT fdo, IN PIRP irp )
{
	NTSTATUS ret;
	PIO_STACK_LOCATION stack;

	//
	// First let all lower-level drivers handle this request.
	//
	ret = ForwardAndWait(fdo, irp);
	if (!NT_SUCCESS(ret))
	{
		irp->IoStatus.Status = ret;
		IoCompleteRequest(irp, IO_NO_INCREMENT);
		return ret;
	}
	//
	// now do whatever we need to do to start the device
	//
	ret = dabusb_StartDevice(fdo);

	irp->IoStatus.Status = ret;
	irp->IoStatus.Information = 0;
	IoCompleteRequest(irp, IO_NO_INCREMENT);
	return ret;
}

NTSTATUS dabusb_dispatch_pnp( IN PDEVICE_OBJECT fdo, IN PIRP irp )
{
	PIO_STACK_LOCATION irpStack;
	pdabusb_t s = fdo->DeviceExtension;
	ULONG fcn;
	NTSTATUS ret = STATUS_SUCCESS;
	PDEVICE_CAPABILITIES DeviceCapabilities;

	irpStack = IoGetCurrentIrpStackLocation (irp);
        ASSERT(irpStack->MajorFunction == IRP_MJ_PNP);
	fcn = irpStack->MinorFunction;
	VLOG(printk(MODSTR "dabusb_dispatch_pnp enter: fcn %lu\n", fcn));
	dabusb_increment_io_count(fdo);
	switch (fcn) {
        case IRP_MN_QUERY_CAPABILITIES:
                VLOG(printk(MODSTR "IRP_MN_QUERY_CAPABILITIES\n"));
                ret = ForwardAndWait(fdo, irp);
                DeviceCapabilities = irpStack->Parameters.DeviceCapabilities.Capabilities;
                DeviceCapabilities->SurpriseRemovalOK = TRUE;
                IoCompleteRequest(irp, IO_NO_INCREMENT);
		dabusb_decrement_io_count(fdo);
		VLOG(printk(MODSTR "dabusb_dispatch_pnp exit: 0x%x\n", ret));
		return ret;

        case IRP_MN_QUERY_PNP_DEVICE_STATE: 
                VLOG(printk(MODSTR"IRP_MN_QUERY_PNP_DEVICE_STATE\n"));
                break;

        case IRP_MN_QUERY_DEVICE_RELATIONS:
                VLOG(printk(MODSTR"IRP_MN_QUERY_DEVICE_RELATIONS\n"));
                break;

        case IRP_MN_START_DEVICE:
                VLOG(printk(MODSTR "IRP_MN_START_DEVICE\n"));
                s->device_state = _started;
		VLOG(printk(MODSTR "device state: started\n"));
                ret = dabusb_HandleStartDevice(fdo,irp);
		dabusb_decrement_io_count(fdo);
		VLOG(printk(MODSTR "dabusb_dispatch_pnp exit: 0x%x\n", ret));
                return ret;

        case IRP_MN_STOP_DEVICE:
                VLOG(printk(MODSTR "IRP_MN_STOP_DEVICE\n"));
                s->device_state = _stopped;
		VLOG(printk(MODSTR "device state: stopped\n"));
		async_cancel_all(fdo);
		abort_pipes(fdo);
                break;

        case IRP_MN_SURPRISE_REMOVAL:
                VLOG(printk(MODSTR "IRP_MN_SURPRISE_REMOVAL\n"));
                goto queryrm_or_surpriserm;

        case IRP_MN_QUERY_REMOVE_DEVICE:
                VLOG(printk(MODSTR "IRP_MN_QUERY_REMOVE_DEVICE\n"));
	queryrm_or_surpriserm:
		if (s->device_state < _started) {
			IoSkipCurrentIrpStackLocation (irp);
			ret = IoCallDriver (s->sdo, irp);
			dabusb_decrement_io_count(fdo);
			VLOG(printk(MODSTR "dabusb_dispatch_pnp exit: 0x%x\n", ret));
			return ret;
		}
		s->device_state = _remove_pending;
		VLOG(printk(MODSTR "device state: remove_pending\n"));
		dabusb_decrement_io_count(fdo);
		async_cancel_all(fdo);
		abort_pipes(fdo);
                KeWaitForSingleObject(&s->NoPendingIoEvent, Suspended, KernelMode, FALSE, NULL);
		IoCopyCurrentIrpStackLocationToNext(irp);
		ret = IoCallDriver(s->sdo, irp);
		VLOG(printk(MODSTR "dabusb_dispatch_pnp exit: 0x%x\n", ret));
		return ret;

        case IRP_MN_REMOVE_DEVICE:
                printk( MODSTR "IRP_MN_REMOVE_DEVICE\n");
		dabusb_decrement_io_count(fdo);
		s->device_state = _removed;
		VLOG(printk(MODSTR "device state: removed\n"));
		async_cancel_all(fdo);
		abort_pipes(fdo);
                IoCopyCurrentIrpStackLocationToNext(irp);
		ret = IoCallDriver(s->sdo, irp);
		dabusb_decrement_io_count(fdo);
		KeWaitForSingleObject(&s->RemoveEvent, Suspended, KernelMode, FALSE, NULL);
		VLOG(printk(MODSTR "IRP_MN_REMOVE_DEVICE: KeWaitForSingleObject succeeded\n"));
		dabusb_RemoveDevice(fdo);
		VLOG(printk(MODSTR "dabusb_dispatch_pnp exit: 0x%x\n", ret));
		return ret;
                
                /* All other PNP IRP's are just passed down the stack by the default handler */
        default:
                VLOG(printk(MODSTR "Passing down unhandled PnP IOCTL MJ=0x%x MN=0x%x\n",
			    irpStack->MajorFunction, irpStack->MinorFunction));
		break;
	}
	if (!NT_SUCCESS(ret)) {
                // if anything went wrong, return failure  without passing Irp down
		irp->IoStatus.Status = ret;
                IoCompleteRequest(irp, IO_NO_INCREMENT);
		dabusb_decrement_io_count(fdo);
		VLOG(printk(MODSTR "dabusb_dispatch_pnp exit: 0x%x\n", ret));
                return ret;
	}
	IoCopyCurrentIrpStackLocationToNext(irp);
	//
	// All PNP_POWER messages get passed to the TopOfStackDeviceObject
	// we were given in PnPAddDevice
	//
	ret = IoCallDriver(s->sdo, irp);
	dabusb_decrement_io_count(fdo);
	VLOG(printk(MODSTR "dabusb_dispatch_pnp exit: 0x%x\n", ret));
        return ret;
}


VOID dabusb_unload(IN PDRIVER_OBJECT drvobj)
{
	VLOG(printk(MODSTR "dabusb_unload enter\n"));
#ifdef CONNECTEVENT
	/* release userspace synchronisation object */
	if (sync_event)
		if (!NT_SUCCESS(ZwClose(sync_event_handle)))
			printk(MODSTR "ZwClose of SynchronisationEvent failed\n");
	sync_event = NULL;
	sync_event_handle = NULL;
#endif
	VLOG(printk(MODSTR "dabusb_unload exit\n"));
}

NTSTATUS dabusb_pnp_add_device(IN PDRIVER_OBJECT DriverObject, IN PDEVICE_OBJECT pdo)
{
	NTSTATUS ret = STATUS_SUCCESS;
	PDEVICE_OBJECT fdo = NULL;
	UNICODE_STRING deviceNameUnicodeString;
	ANSI_STRING ansibuf;
	pdabusb_t s;
	PCHAR buf = kmalloc(80);
	int instance = 0;
        unsigned int i;
        
	if(!buf) {
		printk(MODSTR"dabusb_pnp_add_device: can't kmalloc buf");
		return STATUS_NO_MEMORY;
	}

	VLOG(printk(MODSTR"enter dabusb_PnPAddDevice (%x)\n", ret));

	do {
		sprintf(buf,"\\Device\\"MODNAME"%d",instance);
		RtlInitAnsiString(&ansibuf, buf);
		RtlAnsiStringToUnicodeString(&deviceNameUnicodeString, &ansibuf, TRUE);
		ret = IoCreateDevice(DriverObject, sizeof(dabusb_t), &deviceNameUnicodeString, FILE_DEVICE_UNKNOWN, 0, FALSE, &fdo);
		if (NT_SUCCESS(ret))
			break;
		RtlFreeUnicodeString(&deviceNameUnicodeString);
		instance++;
	} while (instance < 10);

	VLOG(printk(MODSTR "dabusb_pnp_add_device instance: %s\n", buf));

	if (NT_SUCCESS(ret)) {
		s = fdo->DeviceExtension;
		fdo->Flags &= ~DO_DEVICE_INITIALIZING;
		fdo->Flags |= DO_DIRECT_IO | DO_POWER_PAGABLE;
		memset(s, 0, sizeof(dabusb_t));
		s->pdo = pdo;
		s->sdo = IoAttachDeviceToDeviceStack(fdo, pdo);
		// Get a copy of the physical device's capabilities into a
		// DEVICE_CAPABILITIES struct in our device extension;
		// We are most interested in learning which system power states
		// are to be mapped to which device power states for handling
		// IRP_MJ_SET_POWER Irps.
		dabusb_QueryCapabilities(pdo, &s->DeviceCapabilities);
 
                // We want to determine what level to auto-powerdown to; This is the lowest
                //  sleeping level that is LESS than D3;
                // If all are set to D3, auto powerdown/powerup will be disabled.
		s->PowerDownLevel = PowerDeviceUnspecified; // init to disabled
		for (i = PowerSystemSleeping1; i <= PowerSystemSleeping3; i++) {
                        if (s->DeviceCapabilities.DeviceState[i] < PowerDeviceD3)
                                s->PowerDownLevel = s->DeviceCapabilities.DeviceState[i];
		}
                VLOG(printk(MODSTR "s->PowerDownLevel: %x\n",s->PowerDownLevel));

		/* this event is triggered when there is no pending io of any kind and device is removed */
		KeInitializeEvent(&s->RemoveEvent, SynchronizationEvent, FALSE);
		
		/* this event is triggered when there is no pending io  (pending io count == 1) */
		KeInitializeEvent(&s->NoPendingIoEvent, SynchronizationEvent, FALSE);
		/* this event is triggered when self-requested power irps complete */
		KeInitializeEvent(&s->SelfRequestedPowerIrpEvent, NotificationEvent, FALSE);

		KeInitializeEvent(&s->release_ok, SynchronizationEvent, FALSE);

		KeInitializeSemaphore(&s->open_sem, 1, 1);

		KeInitializeSpinLock(&s->cancel_list_lock);
		INIT_LIST_HEAD(&s->cancel_list);

		sprintf(buf, "\\DosDevices\\"MODNAME"%d", instance);
		RtlInitAnsiString(&ansibuf, buf);
		RtlAnsiStringToUnicodeString(&s->deviceLinkUnicodeString, &ansibuf, TRUE);
		ret = IoCreateSymbolicLink(&s->deviceLinkUnicodeString, &deviceNameUnicodeString);
		RtlFreeUnicodeString(&deviceNameUnicodeString);
		ASSERT(s->sdo != NULL);
		dabusb_increment_io_count(fdo);
		// try to power down device until IO actually requested
		dabusb_SelfSuspendOrActivate(fdo, TRUE);
#ifdef CONNECTEVENT
		/* signal user space */
		KeSetEvent(sync_event, 0, FALSE);
#endif
	}
	kfree(buf);

	VLOG(printk(MODSTR "exit dabusb_PnPAddDevice (%x)\n", ret));

	return ret;
}

NTSTATUS dabusb_release(IN PDEVICE_OBJECT fdo, IN PIRP irp)
{
	pdabusb_t s = fdo->DeviceExtension;

	VLOG(printk(MODSTR"dabusb_release enter\n"));
        async_cancel_all(fdo);
	abort_pipes(fdo);
	irp->IoStatus.Status = STATUS_SUCCESS;        
	irp->IoStatus.Information = 0;
	IoCompleteRequest(irp, IO_NO_INCREMENT);
	//KeReleaseSemaphore(&s->open_sem, LOW_REALTIME_PRIORITY, 1, FALSE);
        InterlockedDecrement(&s->opened);
        // try to power down device if this is the last pipe
        dabusb_SelfSuspendOrActivate(fdo, TRUE);
	VLOG(printk(MODSTR"dabusb_release exit\n"));
	return irp->IoStatus.Status;
}

NTSTATUS dabusb_open(IN PDEVICE_OBJECT fdo, IN PIRP irp)
{
	pdabusb_t s = fdo->DeviceExtension;

	VLOG(printk(MODSTR"dabusb_open enter\n"));
        if (InterlockedIncrement(&s->opened) > 1) {
                irp->IoStatus.Status = STATUS_DEVICE_BUSY;
		VLOG(printk(MODSTR"dabusb_open exit: device busy\n"));
                goto done;
        }
        if (s->device_state <= _stop_pending) {
                irp->IoStatus.Status = STATUS_DELETE_PENDING;
                ELOG(printk(MODSTR"dabusb_open exit: stop pending\n"));
                goto done;
        }
        /* try to power up device if its not in D0 */
        dabusb_SelfSuspendOrActivate(fdo, FALSE);

  done:;
        if (irp->IoStatus.Status != STATUS_SUCCESS)
                InterlockedDecrement(&s->opened);
        irp->IoStatus.Information = 0;
        IoCompleteRequest(irp, IO_NO_INCREMENT);
	VLOG(printk(MODSTR "dabusb_open: exit 0x%x\n", irp->IoStatus.Status));
        return irp->IoStatus.Status;
}

NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath )
{
	NTSTATUS ret = STATUS_SUCCESS;
	PDEVICE_OBJECT deviceObject = NULL;
	UNICODE_STRING eventname;

	printk(MODSTR "BaycomUSB Driver V"VERSTR" (c) DF&TS compiled "__DATE__" "__TIME__"\n");

#ifdef CONNECTEVENT
	/* create userspace synchronisation object */
	RtlInitUnicodeString(&eventname, EVENTNAME);
	sync_event = IoCreateNotificationEvent(&eventname, &sync_event_handle);
	if (!sync_event)
		printk(MODSTR "cannot create SynchronizationEvent\n");
#endif

	DriverObject->MajorFunction[IRP_MJ_CREATE] =		dabusb_open;
	DriverObject->MajorFunction[IRP_MJ_CLOSE] =		dabusb_release;
	//DriverObject->MajorFunction[IRP_MJ_READ] =		dabusb_read;
	DriverObject->DriverUnload =				dabusb_unload;
	DriverObject->MajorFunction[IRP_MJ_PNP] =		dabusb_dispatch_pnp;
	DriverObject->MajorFunction[IRP_MJ_POWER] =		dabusb_dispatch_power;
	DriverObject->DriverExtension->AddDevice =		dabusb_pnp_add_device;
	DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] =    baycomusb_ioctl;
	DriverObject->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = baycomusb_internal_ioctl;

	return ret;
}
