/*
 * title:      hidbatt.c
 *
 * purpose:    wdm kernel client interface between HID class and power class
 *
 * Initial checkin for the hid to battery class driver.  This should be
 * the same for both Win 98 and NT 5.  Alpha level source. Requires
 * modified composite battery driver and modified battery class driver for
 * windows 98 support
 *
 */

#include "hidbatt.h"

#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT,DriverEntry)
#endif


// Global
ULONG       HidBattDebug        = HIDBATT_PRINT_NEVER;
USHORT      HidBreakFlag        = HIDBATT_BREAK_NEVER;


// local protos
NTSTATUS
HidBattSystemControl(
    IN PDEVICE_OBJECT   DeviceObject,
    IN PIRP Irp
    );



NTSTATUS
DriverEntry(
    IN PDRIVER_OBJECT DriverObject,
    IN PUNICODE_STRING RegistryPath
    )
{


    NTSTATUS status;

    HidBattPrint (HIDBATT_TRACE, ("HidBatt:DriverEntry\n"));
    HIDDebugBreak(HIDBATT_BREAK_FULL);
    /************************************************************************************/
    /*                                                                                    */
    /*   fill in the slots for the functions in                                            */
    /*   the Driver object.                                                                */
    /*                                                                                    */
    /************************************************************************************/

    DriverObject->MajorFunction[IRP_MJ_CREATE]          = HidBattOpen;
    DriverObject->MajorFunction[IRP_MJ_CLOSE]           = HidBattClose;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]  = HidBattIoControl;
    DriverObject->MajorFunction[IRP_MJ_POWER]           = HidBattPowerDispatch;
    DriverObject->MajorFunction[IRP_MJ_PNP]             = HidBattPnpDispatch;
    DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL]  = HidBattSystemControl;  // pass down to hid class
    DriverObject->DriverUnload                          = HidBattUnload; // this is unloadable with current rev of battery class
    DriverObject->DriverExtension->AddDevice            = HidBattAddDevice;

    return STATUS_SUCCESS;
}

NTSTATUS
HidBattAddDevice(
    IN PDRIVER_OBJECT       DriverObject,
    IN PDEVICE_OBJECT       pHidPdo
    )
{

    BOOL bResult;
    PDEVICE_OBJECT          pBatteryFdo = NULL;
    NTSTATUS                ntStatus;
    CBatteryDevExt *        pDevExt;
    UNICODE_STRING          numberString;
    WCHAR                   numberBuffer[10];
    // enters here with pdo of hidclass - power class object

    HIDDebugBreak(HIDBATT_BREAK_ALWAYS);
    HidBattPrint (HIDBATT_TRACE, ("HidBattAddDevice\n"));


/* sberard - Removed due to changes in hidclass.sys (bug #274422)

    HID_COLLECTION_INFORMATION HidInfo;


    RtlZeroMemory(&HidInfo,sizeof(HID_COLLECTION_INFORMATION));

    ntStatus = DoIoctl(
                pHidPdo,
                IOCTL_HID_GET_COLLECTION_INFORMATION,
                NULL,
                0,
                &HidInfo,
                sizeof(HID_COLLECTION_INFORMATION),
                (CHidDevice *) NULL
                );

    if(NT_ERROR(ntStatus))
    {
        HidBattPrint (HIDBATT_ERROR_ONLY, ("HidBattAddDevice: IOCTL_HID_GET_COLLECTION_INFORMATION failed 0x%08x\n", ntStatus));
        return STATUS_UNSUCCESSFUL;
    }
*/
    // too early to communicate with device, stash hid pdo and complete

    ntStatus = IoCreateDevice(
                DriverObject,
                sizeof (CBatteryDevExt),
                NULL,
                FILE_DEVICE_BATTERY,
                FILE_DEVICE_SECURE_OPEN,
                FALSE,
                &pBatteryFdo
                );

    if (ntStatus != STATUS_SUCCESS) {
        HidBattPrint(HIDBATT_ERROR, ("HidBattCreateFdo: error (0x%x) creating device object\n", ntStatus));
        return(ntStatus);
    }


    // layer the battery pdo to the hid class pdo
    // so that we begin to receive the device irps
    PDEVICE_OBJECT pHidDeviceObject = IoAttachDeviceToDeviceStack(pBatteryFdo,pHidPdo);

    if (!pHidDeviceObject) {
        IoDeleteDevice (pBatteryFdo);
        return STATUS_UNSUCCESSFUL;
    }

    pDevExt = (CBatteryDevExt *) pBatteryFdo->DeviceExtension;
    pDevExt->m_RegistryPath.Length = 0;
    pDevExt->m_RegistryPath.MaximumLength = sizeof(pDevExt->m_RegistryBuffer);
    RtlZeroMemory(&pDevExt->m_RegistryBuffer, sizeof(pDevExt->m_RegistryBuffer));
    pDevExt->m_RegistryPath.Buffer = &pDevExt->m_RegistryBuffer[0]; // set buffer pointer
    pDevExt->m_pBattery = NULL;

    pBatteryFdo->Flags              |=  DO_BUFFERED_IO | DO_POWER_PAGABLE;
    pBatteryFdo->Flags              &=  ~DO_DEVICE_INITIALIZING;
    pDevExt->m_pHidPdo              =   pHidPdo;
    pDevExt->m_pBatteryFdo          =   pBatteryFdo;
    pDevExt->m_pLowerDeviceObject   =   pHidDeviceObject;
    pDevExt->m_eExtType             =   eBatteryDevice;
    pDevExt->m_bFirstStart          =   TRUE;
    pDevExt->m_bJustStarted         =   FALSE;
    pDevExt->m_ulDefaultAlert1      =   (ULONG)-1;

    IoInitializeRemoveLock (&pDevExt->m_RemoveLock, HidBattTag, 10, 20);
    IoInitializeRemoveLock (&pDevExt->m_StopLock, HidBattTag, 10, 20);
    IoAcquireRemoveLock (&pDevExt->m_StopLock, (PVOID) HidBattTag);
    IoReleaseRemoveLockAndWait (&pDevExt->m_StopLock, (PVOID) HidBattTag);

    return STATUS_SUCCESS;

}


NTSTATUS
HidBattOpen(
    IN PDEVICE_OBJECT   pDeviceObject,
    IN PIRP pIrp
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PIO_STACK_LOCATION          irpSp;


    HIDDebugBreak(HIDBATT_BREAK_ALWAYS);
    HidBattPrint (HIDBATT_TRACE, ("HidBattOpen\n"));
    CBatteryDevExt * pDevExt = (CBatteryDevExt *) pDeviceObject->DeviceExtension;

    if (NT_SUCCESS (IoAcquireRemoveLock (&pDevExt->m_RemoveLock, (PVOID) HidBattTag))) {
        IoSkipCurrentIrpStackLocation (pIrp);
        ntStatus = IoCallDriver(pDevExt->m_pLowerDeviceObject, pIrp);

        HidBattPrint (HIDBATT_NOTE, ("HidBattOpen: lower driver returned 0x%08x\n", ntStatus));

        IoReleaseRemoveLock (&pDevExt->m_RemoveLock, (PVOID) HidBattTag);
    } else {
        ntStatus = STATUS_NO_SUCH_DEVICE;
        pIrp->IoStatus.Status = ntStatus;
        IoCompleteRequest (pIrp, IO_NO_INCREMENT);
    }

    return ntStatus;
}



NTSTATUS
HidBattClose(
    IN PDEVICE_OBJECT   pDeviceObject,
    IN PIRP pIrp
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PIO_STACK_LOCATION          irpSp;

    HidBattPrint (HIDBATT_TRACE, ("HidBattClose\n"));
     // get the device extension
    CBatteryDevExt * pDevExt = (CBatteryDevExt *) pDeviceObject->DeviceExtension;

    HidBattCallLowerDriver(ntStatus, pDevExt->m_pLowerDeviceObject, pIrp);
    HidBattPrint (HIDBATT_NOTE, ("HidBattClose: lower driver returned 0x%08x\n", ntStatus));

    return ntStatus;

}


NTSTATUS
HidBattSystemControl(
    IN PDEVICE_OBJECT   pDeviceObject,
    IN PIRP pIrp
    )
{
    HidBattPrint (HIDBATT_TRACE, ("HidBattSystemControl\n"));
    HIDDebugBreak(HIDBATT_BREAK_ALWAYS);

    // all system control calls are passed down for now.
    NTSTATUS ntStatus = STATUS_SUCCESS;
    CBatteryDevExt * pDevExt = (CBatteryDevExt *) pDeviceObject->DeviceExtension;
    HidBattCallLowerDriver(ntStatus,pDevExt->m_pLowerDeviceObject,pIrp);
    return ntStatus;
}


VOID
HidBattUnload(
    IN PDRIVER_OBJECT   pDriverObject
    )
{
    HIDDebugBreak(HIDBATT_BREAK_ALWAYS);
// we can just return, no driver-only (non-device) resources were allocated
    return;
}


NTSTATUS
HidBattPnpDispatch(
    IN PDEVICE_OBJECT   pDeviceObject,
    IN PIRP                pIrp
    )
{

/*++

Routine Description:

    This routine is the dispatch routine for plug and play requests.

Arguments:

    DeviceObject - Pointer to class device object.
    Irp - Pointer to the request packet.

Return Value:

    Status is returned.

--*/

    HIDDebugBreak(HIDBATT_BREAK_ALWAYS);

    PIO_STACK_LOCATION          pIrpStack;
    CBatteryDevExt *            pDevExt;
    NTSTATUS                    ntStatus;
    BOOLEAN                     lockReleased = FALSE;

//    PAGED_CODE();


    ntStatus = STATUS_NOT_SUPPORTED;

    //
    // Get a pointer to the current parameters for this request.  The
    // information is contained in the current stack location.
    //

    pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
    pDevExt = (CBatteryDevExt *) pDeviceObject->DeviceExtension;

    IoAcquireRemoveLock (&pDevExt->m_RemoveLock, (PVOID) HidBattTag);

    //
    // Dispatch minor function
    //
    switch (pIrpStack->MinorFunction)
    {

        case IRP_MN_STOP_DEVICE:
        {
            HidBattPrint (HIDBATT_PNP, ("HidBattPnpDispatch: IRP_MN_STOP_DEVICE\n"));
            ntStatus = HidBattStopDevice(pDeviceObject, pIrp);
            break;
        }   // IRP_MN_STOP_DEVICE

        case IRP_MN_QUERY_DEVICE_RELATIONS:
        {
            HidBattPrint (HIDBATT_PNP, ("HidBattPnpDispatch: IRP_MN_QUERY_DEVICE_RELATIONS - type (%d)\n",
                        pIrpStack->Parameters.QueryDeviceRelations.Type));

            break;
        }   //  IRP_MN_QUERY_DEVICE_RELATIONS

        case IRP_MN_FILTER_RESOURCE_REQUIREMENTS:
        {
            HidBattPrint (HIDBATT_PNP, ("HidBattPnpDispatch: IRP_MN_FILTER_RESOURCE_REQUIREMENTS - type (%d)\n",
                        pIrpStack->Parameters.QueryDeviceRelations.Type));

            break;
        }   //  IRP_MN_FILTER_RESOURCE_REQUIREMENTS

        case IRP_MN_REMOVE_DEVICE:

        {
            HidBattPrint (HIDBATT_PNP, ("HidBattPnpDispatch: IRP_MN_REMOVE_DEVICE\n"));

            HidBattPrint (HIDBATT_PNP, ("HidBattPnpDispatch: Waiting to remove\n"));
            IoReleaseRemoveLockAndWait (&pDevExt->m_RemoveLock, (PVOID) HidBattTag);
            lockReleased = TRUE;

            // then remove device from device stack
            IoDetachDevice(pDevExt->m_pLowerDeviceObject);

            // delete our device
            IoDeleteDevice(pDeviceObject);

            ntStatus = STATUS_SUCCESS;
            break;
        }   //  IRP_MN_REMOVE_DEVICE

        case IRP_MN_SURPRISE_REMOVAL:
        case IRP_MN_QUERY_REMOVE_DEVICE:

        {
            HidBattPrint (HIDBATT_PNP, ("HidBattPnpDispatch: IRP_MN_QUERY_REMOVE_DEVICE\n"));

            ntStatus = HidBattStopDevice(pDeviceObject, pIrp);

            ntStatus = STATUS_SUCCESS;
            break;
        }   //  IRP_MN_QUERY_REMOVE_DEVICE

        case IRP_MN_START_DEVICE:
        {
            HidBattPrint (HIDBATT_PNP, ("HidBattPnpDispatch: IRP_MN_START_DEVICE\n"));
            if (pDevExt->m_bFirstStart) {
                pDevExt->m_bJustStarted = TRUE;
                pDevExt->m_bFirstStart = FALSE;
                ntStatus = STATUS_SUCCESS;
                break;
            }

            // else fall through and do the same thing as the cancel remove.

        }   // IRP_MN_START_DEVICE

        case IRP_MN_CANCEL_REMOVE_DEVICE:
        {
            HidBattPrint (HIDBATT_PNP, ("HidBattPnpDispatch: IRP_MN_CANCEL_REMOVE_DEVICE\n"));

            KEVENT cancelRemoveComplete;

            KeInitializeEvent(&cancelRemoveComplete, SynchronizationEvent, FALSE);
            IoCopyCurrentIrpStackLocationToNext (pIrp);
            IoSetCompletionRoutine (pIrp, HidBattIoCompletion, &cancelRemoveComplete, TRUE, TRUE, TRUE);
            pIrp->IoStatus.Status = STATUS_SUCCESS;
            ntStatus = IoCallDriver (pDevExt->m_pLowerDeviceObject, pIrp);
            HidBattPrint (HIDBATT_PNP, ("HidBattPnpDispatch: IRP_MN_CANCEL_REMOVE_DEVICE, Lower driver status: %08x\n", ntStatus));

            if (ntStatus == STATUS_PENDING) {
                KeWaitForSingleObject (&cancelRemoveComplete, Executive, KernelMode, FALSE, NULL);
                ntStatus = pIrp->IoStatus.Status;
                HidBattPrint (HIDBATT_PNP, ("HidBattPnpDispatch: IRP_MN_CANCEL_REMOVE_DEVICE, Lower driver 2nd status: %08x\n", ntStatus));
            }

            if (NT_SUCCESS (ntStatus)) {
                ntStatus = HidBattInitializeDevice (pDeviceObject, pIrp);
                HidBattPrint (HIDBATT_PNP, ("HidBattPnpDispatch: IRP_MN_CANCEL_REMOVE_DEVICE, Our status: %08x\n", ntStatus));
            }

            pIrp->IoStatus.Status = ntStatus;

            IoCompleteRequest (pIrp, IO_NO_INCREMENT);

            IoReleaseRemoveLock (&pDevExt->m_RemoveLock, (PVOID) HidBattTag);

            return ntStatus;
        }   //  IRP_MN_CANCEL_REMOVE_DEVICE

        case IRP_MN_QUERY_STOP_DEVICE:
        {
            HidBattPrint (HIDBATT_PNP, ("HidBattPnpDispatch: IRP_MN_QUERY_STOP_DEVICE\n"));
            ntStatus = STATUS_SUCCESS;
            break;
        }   //  IRP_MN_QUERY_STOP_DEVICE

        case IRP_MN_CANCEL_STOP_DEVICE:
        {
            HidBattPrint (HIDBATT_PNP, ("HidBattPnpDispatch: IRP_MN_CANCEL_STOP_DEVICE\n"));
            ntStatus = STATUS_SUCCESS;
            break;
        }   //  IRP_MN_CANCEL_STOP_DEVICE

        case IRP_MN_QUERY_RESOURCES:
        {
            HidBattPrint (HIDBATT_PNP, ("HidBattPnpDispatch: IRP_MN_QUERY_RESOURCES\n"));
            break;
        }   //  IRP_MN_QUERY_RESOURCES

        case IRP_MN_READ_CONFIG:
        {
            HidBattPrint (HIDBATT_PNP, ("HidBattPnpDispatch: IRP_MN_READ_CONFIG\n"));
            break;
        }   //  IRP_MN_READ_CONFIG

        case IRP_MN_WRITE_CONFIG:
        {
            HidBattPrint (HIDBATT_PNP, ("HidBattPnpDispatch: IRP_MN_WRITE_CONFIG\n"));
            break;
        }   //  IRP_MN_WRITE_CONFIG

        case IRP_MN_EJECT:
        {
            HidBattPrint (HIDBATT_PNP, ("HidBattPnpDispatch: IRP_MN_EJECT\n"));
            break;
        }   //  IRP_MN_EJECT

        case IRP_MN_SET_LOCK:
        {
            HidBattPrint (HIDBATT_PNP, ("HidBattPnpDispatch: IRP_MN_SET_LOCK\n"));
            break;
        }   //  IRP_MN_SET_LOCK

        case IRP_MN_QUERY_ID:
        {
            HidBattPrint (HIDBATT_PNP, ("HidBattPnpDispatch: IRP_MN_QUERY_ID\n"));
            break;
        }   //  IRP_MN_QUERY_ID

        case IRP_MN_QUERY_CAPABILITIES:
        {
            PDEVICE_CAPABILITIES    deviceCaps;
            HidBattPrint (HIDBATT_PNP, ("HidBattPnpDispatch: IRP_MN_QUERY_CAPABILITIES\n"));

            deviceCaps = pIrpStack->Parameters.DeviceCapabilities.Capabilities;
            deviceCaps->Removable = TRUE;
            deviceCaps->SurpriseRemovalOK = TRUE;

            ntStatus = STATUS_SUCCESS;
            break;
        }   //  IRP_MN_QUERY_CAPABILITIES

        case IRP_MN_QUERY_PNP_DEVICE_STATE:
        {
            HidBattPrint (HIDBATT_PNP, ("HidBattPnpDispatch: IRP_MN_PNP_DEVICE_STATE\n"));

            if (pDevExt->m_bJustStarted == TRUE) {

                pDevExt->m_bJustStarted = FALSE;

                ntStatus = HidBattInitializeDevice (pDeviceObject, pIrp);
            }

            if (!NT_SUCCESS (ntStatus)) {
                HidBattPrint (HIDBATT_PNP, ("HidBattPnpDispatch: HidBattInitializeDevice failed %0x\n", ntStatus));
                pIrp->IoStatus.Information |= PNP_DEVICE_FAILED;
                pIrp->IoStatus.Status = STATUS_SUCCESS;
            }

            break;
        }   //  IRP_MN_PNP_DEVICE_STATE

        default:
        {
            HidBattPrint (HIDBATT_PNP,
                    ("HidBattPnpDispatch: Unimplemented minor %0x\n",
                    pIrpStack->MinorFunction));
            break;
        }
    }

    if (ntStatus != STATUS_NOT_SUPPORTED) {
        pIrp->IoStatus.Status = ntStatus;
    }

    if (NT_SUCCESS(ntStatus) || (ntStatus == STATUS_NOT_SUPPORTED)) {
        HidBattCallLowerDriver (ntStatus, pDevExt->m_pLowerDeviceObject, pIrp);

    } else {
        IoCompleteRequest (pIrp, IO_NO_INCREMENT);
    }

    if (lockReleased == FALSE) {
        IoReleaseRemoveLock (&pDevExt->m_RemoveLock, (PVOID) HidBattTag);
    }

    return ntStatus;
}


NTSTATUS
HidBattPowerDispatch(
    IN PDEVICE_OBJECT   pDeviceObject,
    IN PIRP                pIrp
    )
/*++

Routine Description:

    This routine is the dispatch routine for power requests.

Arguments:

    DeviceObject - Pointer to class device object.
    Irp - Pointer to the request packet.

Return Value:

    Status is returned.

--*/
{
    PIO_STACK_LOCATION            pIrpStack;
    CBatteryDevExt *        pDevExt;
    NTSTATUS                    ntStatus;

    HIDDebugBreak(HIDBATT_BREAK_ALWAYS);

//    PAGED_CODE();

    HidBattPrint ((HIDBATT_TRACE | HIDBATT_POWER), ("HidBattPowerDispatch\n"));

    //
    // Never fail a power IRP, even if we don't do anything.
    //

    ntStatus = STATUS_SUCCESS;
    pIrp->IoStatus.Information = 0;

    //
    // Get a pointer to the current parameters for this request.  The
    // information is contained in the current stack location.
    //

    pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
    pDevExt = (CBatteryDevExt *) pDeviceObject->DeviceExtension;

    //
    // Dispatch minor function
    //
    // this switch currently does no dispatches, and is expanded only for
    // documentary purposes
    switch (pIrpStack->MinorFunction) {

    case IRP_MN_WAIT_WAKE: {
            HidBattPrint (HIDBATT_POWER, ("HidBattPowerDispatch: IRP_MN_WAIT_WAKE\n"));
            break;
        }

    case IRP_MN_POWER_SEQUENCE: {
            HidBattPrint (HIDBATT_POWER, ("HidBattPowerDispatch: IRP_MN_POWER_SEQUENCE\n"));
            break;
        }

    case IRP_MN_SET_POWER: {
            HidBattPrint (HIDBATT_POWER, ("HidBattPowerDispatch: IRP_MN_SET_POWER\n"));
            if (pIrpStack->Parameters.Power.Type == SystemPowerState &&
                pIrpStack->Parameters.Power.State.SystemState >= PowerSystemShutdown) {

                if (NT_SUCCESS(IoAcquireRemoveLock (&pDevExt->m_StopLock, (PVOID) HidBattTag)) )
                {
                    //
                    // Write default RemainingCapcitylimit back to UPS so when the system reboots,
                    // the data returned by the device will be correct.
                    //
                    pDevExt->m_pBattery->GetSetValue(REMAINING_CAPACITY_LIMIT_INDEX,
                                                     &pDevExt->m_ulDefaultAlert1,TRUE);
                    
                    IoReleaseRemoveLock (&pDevExt->m_StopLock, (PVOID) HidBattTag);
                }
            }
            break;
        }

    case IRP_MN_QUERY_POWER: {
            HidBattPrint (HIDBATT_POWER, ("HidBattPowerDispatch: IRP_MN_QUERY_POWER\n"));
            break;
        }

    default: {

            HidBattPrint(HIDBATT_LOW, ("HidBattPowerDispatch: minor %d\n",
                    pIrpStack->MinorFunction));

            break;
        }
    }

    PoStartNextPowerIrp(pIrp); // inform system we are done with this irp
    IoSkipCurrentIrpStackLocation(pIrp);
    ntStatus = PoCallDriver(pDevExt->m_pLowerDeviceObject,pIrp);

    return ntStatus;
}

NTSTATUS HidBattSetInformation(
    IN PVOID Context,
    IN ULONG BatteryTag,
    IN BATTERY_SET_INFORMATION_LEVEL Level,
    IN PVOID Buffer OPTIONAL
    )

{
/*
 Routine Description:

    Called by the class driver to set the battery's charge/discharge state.
    The smart battery does not support the critical bias function of this
    call.

Arguments:

    Context         - Miniport context value for battery

    BatteryTag      - Tag of current battery

    Level           - Action being asked for

Return Value:

    NTSTATUS

--*/
    // charge and discharge forcing not supported for UPS's
    HidBattPrint (HIDBATT_TRACE, ("HidBattSetInformation\n"));
    HIDDebugBreak(HIDBATT_BREAK_ALWAYS);
    return STATUS_UNSUCCESSFUL;
}