/*
 * title:      hidbattpnp.cpp
 *
 * purpose:    support for plug and play routines
 *
 * 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"

NTSTATUS HidBattInitializeDevice (PDEVICE_OBJECT pBatteryFdo, PIRP pIrp)
{

    NTSTATUS            ntStatus;
    CBatteryDevExt *    pDevExt = (CBatteryDevExt *) pBatteryFdo->DeviceExtension;
    PIO_STACK_LOCATION  pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
    ULONG               ulBatteryStatus;
    bool                bResult;
    BATTERY_MINIPORT_INFO   BattInit;
    UNICODE_STRING      Name;
    ULONG               ulBufferLength = 0;
    PWCHAR              pBuffer = NULL;
    CBattery *          pBattery;
    PFILE_OBJECT        fileObject;
    OBJECT_ATTRIBUTES   objectAttributes;
    HANDLE              fileHandle;
    IO_STATUS_BLOCK     ioStatus;

    HIDDebugBreak(HIDBATT_BREAK_ALWAYS);

    HidBattPrint (HIDBATT_PNP, ("HidBattInitializeDevice: Sending Irp (0x%x) to Pdo\n", pIrp));

    // now get file object using KenRay's method from mouclass
    ntStatus = IoGetDeviceProperty (
                     pDevExt->m_pHidPdo,
                     DevicePropertyPhysicalDeviceObjectName,
                     ulBufferLength,
                     pBuffer,
                     &ulBufferLength);
    if(ntStatus != STATUS_BUFFER_TOO_SMALL)
    {
        // only thing we expect with a zero buffer is this error,
        // any other error must be fatal
        return STATUS_UNSUCCESSFUL;
    }


    pBuffer = (PWCHAR) ExAllocatePoolWithTag (NonPagedPool, ulBufferLength, HidBattTag);

    if (NULL == pBuffer) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    ntStatus =  IoGetDeviceProperty (
                  pDevExt->m_pHidPdo,
                  DevicePropertyPhysicalDeviceObjectName,
                  ulBufferLength,
                  pBuffer,
                  &ulBufferLength);

    if(NT_ERROR(ntStatus))
    {
        ExFreePool(pBuffer);
        return ntStatus;

    }


    Name.MaximumLength = (USHORT) ulBufferLength;
    Name.Length = (USHORT) ulBufferLength - sizeof (UNICODE_NULL);
    Name.Buffer = pBuffer;

    pDevExt->m_OpeningThread = KeGetCurrentThread();

    //
    // Initialize the object attributes to open the device.
    //

    InitializeObjectAttributes( &objectAttributes,
                                &Name,
                                0,
                                (HANDLE) NULL,
                                (PSECURITY_DESCRIPTOR) NULL );

    ntStatus = ZwOpenFile( &fileHandle,
                         FILE_ALL_ACCESS,
                         &objectAttributes,
                         &ioStatus,
                         FILE_SHARE_WRITE | FILE_SHARE_READ, 
                         FILE_NON_DIRECTORY_FILE );

    ExFreePool (pBuffer);

    if (NT_SUCCESS( ntStatus )) {

        //
        // The open operation was successful.  Dereference the file handle
        // and obtain a pointer to the device object for the handle.
        //

        ntStatus = ObReferenceObjectByHandle( fileHandle,
                                            0,
                                            *IoFileObjectType,
                                            KernelMode,
                                            (PVOID *) &pDevExt->m_pHidFileObject,
                                            NULL );

        ZwClose( fileHandle );
    }

    pDevExt->m_OpeningThread = NULL;
    if(NT_ERROR(ntStatus))
    {
        return ntStatus;
    }

    // now init new hid deviceclass object for this device
    CHidDevice * pHidDevice = new (NonPagedPool, HidBattTag) CHidDevice;  // setup a new hid device

    if (!pHidDevice) {
      HidBattPrint(HIDBATT_ERROR, ("HidBattInitializeDevice: error allocating CHidDevice"));
      return STATUS_UNSUCCESSFUL;
    }

    pHidDevice->m_pFCB = pDevExt->m_pHidFileObject; // put usable file object into hid device
    pHidDevice->m_pLowerDeviceObject = pDevExt->m_pLowerDeviceObject;
    pHidDevice->m_pDeviceObject = pDevExt->m_pBatteryFdo;
    pHidDevice->m_pReadIrp = NULL;
    bResult = pHidDevice->OpenHidDevice(pDevExt->m_pHidPdo); // initialize the members of this device

    if(!bResult)
    {
        delete pHidDevice;
        return STATUS_UNSUCCESSFUL;
    }

    // check if this has a power page, ups application collection

    if(pHidDevice->m_UsagePage != UsagePowerPage || pHidDevice->m_UsageID != UsageUPS)
    {
        delete pHidDevice;
        return STATUS_UNSUCCESSFUL;
    }

    //
    // Initialize Fdo device extension data
    //

    // create the battery object
    pBattery            = new (NonPagedPool, HidBattTag) CBattery(pHidDevice);

    if (!pBattery){
      HidBattPrint(HIDBATT_ERROR, ("HidBattInitializeDevice: error allocating CBattery"));
      return STATUS_UNSUCCESSFUL;
    }
    
    // and initialize battery values
    // now init in both new and replug states
    pBattery->m_pCHidDevice    = pHidDevice;  // save init'ed hid device object
    bResult = pBattery->InitValues();
    if(!bResult)
    {
        return STATUS_UNSUCCESSFUL;
    }

    // Attach to the Class Driver
    RtlZeroMemory (&BattInit, sizeof(BattInit));
    BattInit.MajorVersion        = BATTERY_CLASS_MAJOR_VERSION;
    BattInit.MinorVersion        = BATTERY_CLASS_MINOR_VERSION;
    BattInit.Context             = pDevExt;
    BattInit.QueryTag            = HidBattQueryTag;
    BattInit.QueryInformation    = HidBattQueryInformation;
    BattInit.SetInformation      = HidBattSetInformation;
    BattInit.QueryStatus         = HidBattQueryStatus;
    BattInit.SetStatusNotify     = HidBattSetStatusNotify;
    BattInit.DisableStatusNotify = HidBattDisableStatusNotify;

    BattInit.Pdo                 = pDevExt->m_pHidPdo;
    BattInit.DeviceName          = &pDevExt->m_pBatteryName->m_String;

    pHidDevice->m_pEventHandler = HidBattNotifyHandler;
    pHidDevice->m_pEventContext = (PVOID) pDevExt;

    //
    // save battery in device extension
    // This indicates that we are ready for IO.
    //
    pDevExt->m_pBattery = pBattery;

    //
    // Initialize stop lock
    //
    IoInitializeRemoveLock (&pDevExt->m_StopLock, HidBattTag, 10, 20);

    // and finally we can now start actively polling the device
    // start the read/notification process
    ntStatus = pBattery->m_pCHidDevice->ActivateInput();
    if(!NT_SUCCESS(ntStatus))
    {
        delete pHidDevice;
        HidBattPrint(HIDBATT_ERROR, ("HidBattInitializeDevice: error (0x%x) in ActivateInput.\n", ntStatus));
        return ntStatus;
    }

    ntStatus = BatteryClassInitializeDevice (&BattInit, &pBattery->m_pBatteryClass);
    if (!NT_SUCCESS(ntStatus))
    {
        //
        //  if we can't attach to class driver we're toast
        //
        delete pHidDevice;
        HidBattPrint(HIDBATT_ERROR, ("HidBattInitializeDevice: error (0x%x) registering with class\n", ntStatus));
        return ntStatus;
    }

    HidBattPrint(HIDBATT_TRACE, ("HidBattInitializeDevice: returned ok\n"));
    return ntStatus;
}



NTSTATUS
HidBattStopDevice(
    IN PDEVICE_OBJECT pBatteryFdo,
    IN PIRP           pIrp
    )
/*++

Routine Description:

    This routine is called when we receive a IRP_MN_STOP_DEVICE. It just sends the
    request down the chain of drivers to the Pdo and waits for a response.

Arguments:

    Fdo - a pointer to the fdo for this driver
    Irp - Pointer to the request packet.

Return Value:

    Status is returned.

--*/
{
    CBatteryDevExt * pDevExt = (CBatteryDevExt *) pBatteryFdo->DeviceExtension;
    KEVENT              pdoStoppedEvent;
    PIO_STACK_LOCATION  pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
    NTSTATUS            ntStatus;

    HIDDebugBreak(HIDBATT_BREAK_ALWAYS);
    HidBattPrint ((HIDBATT_TRACE | HIDBATT_PNP), ("HidBattStopDevice\n"));

    if (!NT_SUCCESS (IoAcquireRemoveLock (&pDevExt->m_StopLock, (PVOID) HidBattTag))) {
        //
        // Don't release twice.
        //
        return STATUS_SUCCESS;
    }

    pDevExt->m_bJustStarted = FALSE;

    HidBattPrint (HIDBATT_PNP, ("HidBattStopDevice: Waiting to remove\n"));
    IoReleaseRemoveLockAndWait (&pDevExt->m_StopLock, (PVOID) HidBattTag);

    if (pDevExt->m_pBattery && pDevExt->m_pBattery->m_pBatteryClass) {
        BatteryClassUnload(pDevExt->m_pBattery->m_pBatteryClass);
    }

    if (pDevExt->m_pBattery && pDevExt->m_pBattery->m_pCHidDevice &&
        pDevExt->m_pBattery->m_pCHidDevice->m_pReadIrp) {
        IoCancelIrp (pDevExt->m_pBattery->m_pCHidDevice->m_pReadIrp);
    }

    if (pDevExt->m_pBattery && pDevExt->m_pBattery->m_pCHidDevice &&
        pDevExt->m_pBattery->m_pCHidDevice->m_pThreadObject) {

        //
        // Clean up worker thread.
        //

        KeWaitForSingleObject (
               pDevExt->m_pBattery->m_pCHidDevice->m_pThreadObject,
               Executive,
               KernelMode,
               FALSE,
               NULL
               );
        HidBattPrint (HIDBATT_PNP, ("HidBattStopDevice: Done Waiting to remove\n"));

        ObDereferenceObject (pDevExt->m_pBattery->m_pCHidDevice->m_pThreadObject);
    }

    if (pDevExt->m_pBattery && pDevExt->m_pBattery->m_pCHidDevice &&
        pDevExt->m_pBattery->m_pCHidDevice->m_pReadIrp) {
        IoFreeIrp(pDevExt->m_pBattery->m_pCHidDevice->m_pReadIrp); // clean up irp
        pDevExt->m_pBattery->m_pCHidDevice->m_pReadIrp = NULL;
    }

    //
    // Write default RemainingCapcitylimit back to UPS so the next time we enumerate
    // the device, we'll read back the right data.
    //
    pDevExt->m_pBattery->GetSetValue(REMAINING_CAPACITY_LIMIT_INDEX,
                                     &pDevExt->m_ulDefaultAlert1,TRUE);

    // dereference our file object, if present
    if(pDevExt->m_pHidFileObject) {
        ObDereferenceObject(pDevExt->m_pHidFileObject);
        pDevExt->m_pBattery->m_pCHidDevice->m_pFCB = NULL;
    }


    if (pDevExt->m_pBatteryName) {
        delete pDevExt->m_pBatteryName;
        pDevExt->m_pBatteryName = NULL;
    }

    if (pDevExt->m_pBattery) {
        delete pDevExt->m_pBattery;
        pDevExt->m_pBattery = NULL;
    }

    ntStatus = STATUS_SUCCESS;

    pDevExt->m_bIsStarted = FALSE;

    return STATUS_SUCCESS;
}