#include "RBC9-SpaceNav.h"

#pragma PAGEDCODE
static VOID RBC9_cancel_read_report(IN PDEVICE_OBJECT pDev_obj, IN PIRP pIrp)
{
    PDEVICE_EXTENSION		pDevExt;
    KIRQL irql;

 //   DBG_ENTER(("%p, %p", pDev_obj, pIrp));

    pDevExt = GET_MINIDRIVER_DEVICE_EXTENSION(pDev_obj);

    IoReleaseCancelSpinLock(pIrp->CancelIrql);

    KeAcquireSpinLock(&pDevExt->Pending_Read_irp_Lock, &irql);
    RemoveEntryList(&pIrp->Tail.Overlay.ListEntry);
    InitializeListHead(&pIrp->Tail.Overlay.ListEntry);
    KeReleaseSpinLock(&pDevExt->Pending_Read_irp_Lock, irql);

    RBC9_COMPLETE_REQUEST(pIrp, 0, STATUS_CANCELLED, IO_NO_INCREMENT);

  //  DBG_LEAVE(("void"));
}

#pragma LOCKEDCODE

__inline static PIRP RBC9_dequeue_Pending_Read_irp(IN PDEVICE_OBJECT pDev_obj)
{
    PDEVICE_EXTENSION		pDevExt;
    PIRP					pIrp;
    PLIST_ENTRY				entry;
    KIRQL					irql;

   // DBG_ENTER(("%p", pDev_obj));

    pDevExt = GET_MINIDRIVER_DEVICE_EXTENSION(pDev_obj);

    do {
        if(IsListEmpty(&pDevExt->Pending_Read_irp)) {
           // DBG_LEAVE(("NULL: Pending_Read_irp is empty"));
            return NULL;
        }

        KeAcquireSpinLock(&pDevExt->Pending_Read_irp_Lock, &irql);
        entry = RemoveHeadList(&pDevExt->Pending_Read_irp);
        InitializeListHead(entry);
        KeReleaseSpinLock(&pDevExt->Pending_Read_irp_Lock, irql);

        pIrp = CONTAINING_RECORD(entry, IRP, Tail.Overlay.ListEntry);
        if(IoSetCancelRoutine(pIrp, NULL) == NULL) {
            DBG_OUT(("already cancelling: %p", pIrp));
            pIrp = NULL;
        }
    } while(pIrp == NULL);

   // DBG_LEAVE(("%p", pIrp));
    return pIrp;
}
__inline static void RBC9_enqueue_Pending_Read_irp(IN PDEVICE_OBJECT pDev_obj, IN PIRP pIrp)
{
    PDEVICE_EXTENSION		pDevExt;
    KIRQL irql;

  //  DBG_ENTER(("%p, %p", pDev_obj, pIrp));

    pDevExt = GET_MINIDRIVER_DEVICE_EXTENSION(pDev_obj);

    InitializeListHead(&pIrp->Tail.Overlay.ListEntry);
    IoSetCancelRoutine(pIrp, RBC9_cancel_read_report);

    KeAcquireSpinLock(&pDevExt->Pending_Read_irp_Lock, &irql);
    InsertTailList(&pDevExt->Pending_Read_irp, &pIrp->Tail.Overlay.ListEntry);
    KeReleaseSpinLock(&pDevExt->Pending_Read_irp_Lock, irql);

  //  DBG_LEAVE(("void"));
}

__inline static NTSTATUS RBC9_complete_read_report(IN PDEVICE_OBJECT pDev_obj, IN PIRP pIrp)
{
    PDEVICE_EXTENSION		pDevExt;
    PIO_STACK_LOCATION		irp_sp;
    ULONG					size;
    UCHAR					*buf;
    NTSTATUS				Status;

  //  DBG_ENTER(("%p, %p", pDev_obj, pIrp));

    pDevExt = GET_MINIDRIVER_DEVICE_EXTENSION(pDev_obj);

    irp_sp = IoGetCurrentIrpStackLocation(pIrp);

    size = irp_sp->Parameters.DeviceIoControl.OutputBufferLength;
    buf = pIrp->UserBuffer;

    Status = RBC9_hid_read_rep(pDev_obj, buf, &size);
    if(! NT_SUCCESS(Status)) {
        RBC9_COMPLETE_REQUEST(pIrp, 0, Status, IO_NO_INCREMENT);

        DBG_LEAVE(("%x", Status));
        return Status;
    }

    RBC9_COMPLETE_REQUEST(pIrp, size, STATUS_SUCCESS, IO_NO_INCREMENT);

 //   DBG_LEAVE(("STATUS_SUCCESS"));
    return STATUS_SUCCESS;
}


NTSTATUS RBC9_ioctl_get_dev_desc(IN PDEVICE_OBJECT pDev_obj, IN PIRP pIrp)
{
	PDEVICE_EXTENSION		pDevExt;
    PIO_STACK_LOCATION		irp_sp;
    PHID_DESCRIPTOR			hid_desc;

    DBG_ENTER(("%p, %p", pDev_obj, pIrp));

    pDevExt = GET_MINIDRIVER_DEVICE_EXTENSION(pDev_obj);
    irp_sp = IoGetCurrentIrpStackLocation(pIrp);
    hid_desc = (PHID_DESCRIPTOR)pIrp->UserBuffer;

    if(irp_sp->Parameters.DeviceIoControl.OutputBufferLength <
       sizeof(*hid_desc)) {
        RBC9_COMPLETE_REQUEST(pIrp, 0, STATUS_BUFFER_TOO_SMALL, IO_NO_INCREMENT);

        DBG_LEAVE(("STATUS_BUFFER_TOO_SMALL"));
        return STATUS_BUFFER_TOO_SMALL;
    }

    RtlZeroMemory(hid_desc, sizeof(*hid_desc));
    hid_desc->bLength = sizeof(*hid_desc);
    hid_desc->bDescriptorType = HID_HID_DESCRIPTOR_TYPE;
    hid_desc->bcdHID = HID_REVISION;
    hid_desc->bCountry = 0;
    hid_desc->bNumDescriptors = 1;
    hid_desc->DescriptorList[0].bReportType = HID_REPORT_DESCRIPTOR_TYPE;
    hid_desc->DescriptorList[0].wReportLength = pDevExt->Report_Descriptor_Size;
//	DBG_OUT(("pDevExt->Report_Descriptor_Size %d",pDevExt->Report_Descriptor_Size));

    RBC9_COMPLETE_REQUEST(pIrp, sizeof(*hid_desc), STATUS_SUCCESS, IO_NO_INCREMENT);

    DBG_LEAVE(("STATUS_SUCCESS"));
    return STATUS_SUCCESS;
}

NTSTATUS RBC9_ioctl_get_dev_attr(IN PDEVICE_OBJECT pDev_obj,	IN PIRP pIrp)
{
	PDEVICE_EXTENSION		pDevExt;
    PIO_STACK_LOCATION irp_sp;
    PHID_DEVICE_ATTRIBUTES hid_attr;
    
    DBG_ENTER(("%p, %p", pDev_obj, pIrp));

       pDevExt = GET_MINIDRIVER_DEVICE_EXTENSION(pDev_obj);
    irp_sp = IoGetCurrentIrpStackLocation(pIrp);
    hid_attr = (PHID_DEVICE_ATTRIBUTES)pIrp->UserBuffer;

    if(irp_sp->Parameters.DeviceIoControl.OutputBufferLength <
       sizeof(*hid_attr)) {
        RBC9_COMPLETE_REQUEST(pIrp, 0, STATUS_BUFFER_TOO_SMALL, IO_NO_INCREMENT);

        DBG_LEAVE(("%u < %d",
                   irp_sp->Parameters.DeviceIoControl.OutputBufferLength,
                   sizeof(*hid_attr)));
        return STATUS_BUFFER_TOO_SMALL;
    }

    hid_attr->Size = sizeof(*hid_attr);
    hid_attr->VendorID = pDevExt->DeviceDescriptor.idVendor;
    hid_attr->ProductID = pDevExt->DeviceDescriptor.idProduct;
    hid_attr->VersionNumber = pDevExt->DeviceDescriptor.bcdDevice;

//DBG_OUT(("VendorId and Product Id to test !!!!!!!!!!!!!!!!!!!!"));
//	hid_attr->VendorID = 0x0800;
 //  hid_attr->ProductID = 0x0777;
 //   hid_attr->VersionNumber = 0x0666;
	

    RBC9_COMPLETE_REQUEST(pIrp, sizeof(*hid_attr), STATUS_SUCCESS, IO_NO_INCREMENT);

   DBG_LEAVE(("STATUS_SUCCESS"));
    return STATUS_SUCCESS;
}

NTSTATUS RBC9_ioctl_get_rep_desc(IN PDEVICE_OBJECT pDev_obj,	IN PIRP pIrp)
{
	PIO_STACK_LOCATION irp_sp;
    ULONG size;
    UCHAR *buf;
    NTSTATUS Status;

    DBG_ENTER(("%p, %p", pDev_obj, pIrp));

    irp_sp = IoGetCurrentIrpStackLocation(pIrp);

    size = irp_sp->Parameters.DeviceIoControl.OutputBufferLength;
    buf = pIrp->UserBuffer;

    Status = RBC9_hid_get_rep_desc(pDev_obj, buf, &size);
	
    if(! NT_SUCCESS(Status)) {
        RBC9_COMPLETE_REQUEST(pIrp, 0, Status, IO_NO_INCREMENT);

        DBG_LEAVE(("%x", Status));
        return Status;
    }

    RBC9_COMPLETE_REQUEST(pIrp, size, STATUS_SUCCESS, IO_NO_INCREMENT);

    DBG_LEAVE(("STATUS_SUCCESS"));
    return STATUS_SUCCESS;
}

NTSTATUS RBC9_ioctl_read_rep(IN PDEVICE_OBJECT pDev_obj,	IN PIRP pIrp)
{
	PDEVICE_EXTENSION		pDevExt;

  //  DBG_ENTER(("%p, %p", pDev_obj, pIrp));

    pDevExt = GET_MINIDRIVER_DEVICE_EXTENSION(pDev_obj);
	
    if(pDevExt->Pnp_State != DEVICE_STATE_STARTED) {
        /* device is not started */
        RBC9_COMPLETE_REQUEST(pIrp, 0, STATUS_DEVICE_NOT_CONNECTED, IO_NO_INCREMENT);

        DBG_LEAVE(("STATUS_DEVICE_NOT_CONNECTED: device is not started"));
		
		RBC9_device_start(pDev_obj);

        return STATUS_DEVICE_NOT_CONNECTED;
    }
	

    /* enqueue current irp */
    IoMarkIrpPending(pIrp);
    RBC9_enqueue_Pending_Read_irp(pDev_obj, pIrp);
	
	/* indicate to complete request */
    KeSetEvent(&pDevExt->hid_worker_wakeup_event, IO_NO_INCREMENT, FALSE);

  //  DBG_LEAVE(("STATUS_PENDING"));
    return STATUS_PENDING;
}

NTSTATUS RBC9_ioctl_write_rep(IN PDEVICE_OBJECT pDev_obj,	IN PIRP pIrp)
{
	PHID_XFER_PACKET xfer_packet;
    ULONG size;
    UCHAR *buf;
    UCHAR id;
    NTSTATUS Status;

   // DBG_ENTER(("%p, %p", pDev_obj, pIrp));

    xfer_packet = (PHID_XFER_PACKET)pIrp->UserBuffer;
    size = xfer_packet->reportBufferLen;
    buf = xfer_packet->reportBuffer;
    id = xfer_packet->reportId;

    Status = RBC9_hid_write_rep(pDev_obj, buf, &size, id);
    if(! NT_SUCCESS(Status)) {
        RBC9_COMPLETE_REQUEST(pIrp, 0, Status, IO_NO_INCREMENT);

        DBG_LEAVE(("%x", Status));
        return Status;
    }

    RBC9_COMPLETE_REQUEST(pIrp, size, STATUS_SUCCESS, IO_NO_INCREMENT);

  //  DBG_LEAVE(("STATUS_SUCCESS"));
    return STATUS_SUCCESS;
}


VOID RBC9_notify_input_report_change(IN PDEVICE_OBJECT pDev_obj)
{
    PDEVICE_EXTENSION		pDevExt;
    PIRP					pIrp;
    NTSTATUS				Status;

   // DBG_ENTER(("%p", pDev_obj));

    pDevExt = GET_MINIDRIVER_DEVICE_EXTENSION(pDev_obj);

    while(pDevExt->hid_input_changed) {
        pIrp = RBC9_dequeue_Pending_Read_irp(pDev_obj);
        if(pIrp == NULL) {
           // DBG_OUT(("no more irp exist"));
            break;
        }

        Status = RBC9_complete_read_report(pDev_obj, pIrp);
        if(! NT_SUCCESS(Status)) {
            DBG_OUT(("%x: complete_read_report failed",Status));
            break;
        }
    }

   // DBG_LEAVE(("void"));
}

VOID RBC9_notify_input_report_stop(IN PDEVICE_OBJECT pDev_obj)
{
	PDEVICE_EXTENSION		pDevExt;
    PIRP					pIrp;

   // DBG_ENTER(("%p", pDev_obj));

    pDevExt = GET_MINIDRIVER_DEVICE_EXTENSION(pDev_obj);

    /* cancel all requests */
    while(pIrp = RBC9_dequeue_Pending_Read_irp(pDev_obj),
          pIrp != NULL) {
       // DBG_OUT(("cancel irp: %p", pIrp));
        RBC9_COMPLETE_REQUEST(pIrp, 0, STATUS_CANCELLED, IO_NO_INCREMENT);
    }

   // DBG_LEAVE(("void"));
}



