/*++

Copyright (c) 1997-1998 Microsoft Corporation, All Rights Reserved

Module Name:

    detect.c

Abstract:

   Detection of surprise removal of the mouse.
   
Environment:

   Kernel mode only.

Notes:

Revision History:

--*/

#include "stdarg.h"
#include "stdio.h"
#include "string.h"
#include "ntddk.h"
#include "mouser.h"
#include "sermlog.h"
#include "debug.h"

VOID
SerialMouseSerialMaskEventWorker(
    PDEVICE_OBJECT DeviceObject,
    PIO_WORKITEM   Item
    );

NTSTATUS
SerialMouseSerialMaskEventComplete (
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP           Irp,
    IN PVOID          Context
    );

NTSTATUS
SerialMouseSendWaitMaskIrp(
    IN PDEVICE_EXTENSION DeviceExtension
    );

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, SerialMouseSerialMaskEventWorker)
#pragma alloc_text(PAGE, SerialMouseStartDetection)
#pragma alloc_text(PAGE, SerialMouseStopDetection)
#pragma alloc_text(PAGE, SerialMouseSendWaitMaskIrp)
#endif

NTSTATUS
SerialMouseSendWaitMaskIrp(
    IN PDEVICE_EXTENSION DeviceExtension
    )
{
    PIRP                irp;
    PIO_STACK_LOCATION  next;
    NTSTATUS            status;

    PAGED_CODE();

    irp = DeviceExtension->DetectionIrp;

    DeviceExtension->SerialEventBits = 0x0;

    //
    // Will be released in the completion routine
    //
    status = IoAcquireRemoveLock (&DeviceExtension->RemoveLock, irp);
    if (!NT_SUCCESS(status)) {
        return status;
    }
    
    IoReuseIrp(irp, STATUS_SUCCESS);

    next = IoGetNextIrpStackLocation(irp);
    next->MajorFunction = IRP_MJ_DEVICE_CONTROL;
    next->Parameters.DeviceIoControl.IoControlCode = IOCTL_SERIAL_WAIT_ON_MASK;
    next->Parameters.DeviceIoControl.OutputBufferLength = sizeof(ULONG);
    irp->AssociatedIrp.SystemBuffer = &DeviceExtension->SerialEventBits;
    
    //
    // Hook a completion routine for when the device completes.
    //
    IoSetCompletionRoutine(irp,
                           SerialMouseSerialMaskEventComplete,
                           DeviceExtension,
                           TRUE,
                           TRUE,
                           TRUE);

    return IoCallDriver(DeviceExtension->TopOfStack, irp);
}

VOID
SerialMouseStartDetection(
    PDEVICE_EXTENSION DeviceExtension
    )
/*++

Routine Description

This will cancel any previous set on the timer and queue the timer for
DetectionTimeout # of seconds and repeatedly trigger the timer every
DetectionTimeout # of seconds.

Arguments:

    DeviceExtension - pointer to the device extension


Return Value:

    None
    
  --*/        
{
    IO_STATUS_BLOCK     iosb;
    KEVENT              event;
    ULONG               waitMask, bits = 0x0;
    NTSTATUS            status;
    PDEVICE_OBJECT      self;
    ULONG               statusBits[] = {
                            SERIAL_DSR_STATE,
                            SERIAL_CTS_STATE,
                            0x0
                        };
    ULONG               eventBits[] = {
                            SERIAL_EV_DSR,
                            SERIAL_EV_CTS,
                        };
    int                 i;

    PAGED_CODE();

    //
    // Check to see if removal detection was turned off in the registry
    //
    if (DeviceExtension->WaitEventMask == 0xffffffff) {
        DeviceExtension->DetectionSupported = FALSE;
        return;
    }

    KeInitializeEvent(&event, NotificationEvent, FALSE);

    if (!DeviceExtension->WaitEventMask) {
        status = SerialMouseIoSyncIoctlEx(IOCTL_SERIAL_GET_MODEMSTATUS,
                                          DeviceExtension->TopOfStack, 
                                          &event,
                                          &iosb,
                                          NULL,
                                          0,
                                          &bits,
                                          sizeof(ULONG)
                                          );
    
        Print(DeviceExtension, DBG_SS_NOISE, 
              ("get modem status, NTSTATUS = 0x%x, bits = 0x%x\n",
              status, bits));

        if (!NT_SUCCESS(status) || !bits) {
            Print(DeviceExtension, DBG_SS_ERROR, 
                  ("modem status failed, status = 0x%x, bits are 0x%x\n",
                  status, bits));

            DeviceExtension->ModemStatusBits = 0x0;
            DeviceExtension->DetectionSupported = FALSE;

            return;
        }

        DeviceExtension->ModemStatusBits = bits;

        for (i = 0, waitMask = 0x0; statusBits[i] != 0x0; i++) {
            if (bits & statusBits[i]) {
                waitMask |= eventBits[i];
            }
        }

        Print(DeviceExtension, DBG_SS_NOISE, 
              ("event wait bits are 0x%x\n", waitMask));

    }
    else {
        waitMask = DeviceExtension->WaitEventMask;
    }

    status = SerialMouseIoSyncIoctlEx(IOCTL_SERIAL_SET_WAIT_MASK,
                                      DeviceExtension->TopOfStack,
                                      &event,
                                      &iosb,
                                      &waitMask,
                                      sizeof(ULONG),
                                      NULL,
                                      0);

    if (!NT_SUCCESS(status)) {
        Print(DeviceExtension, DBG_SS_ERROR, 
              ("set mask failed, status = 0x%x\n", status));

        DeviceExtension->DetectionSupported = FALSE;
        return;
    }

    self = DeviceExtension->Self;

    if (!DeviceExtension->DetectionIrp) {
        if (!(DeviceExtension->DetectionIrp =
                IoAllocateIrp(self->StackSize, FALSE))) {
            DeviceExtension->DetectionSupported = FALSE;
            return;
        }
    }

    status = SerialMouseSendWaitMaskIrp(DeviceExtension);

    Print(DeviceExtension, DBG_SS_NOISE, ("set wait event status = 0x%x\n", status));

    if (NT_SUCCESS(status)) {
        DeviceExtension->DetectionSupported = TRUE;
    }
    else {
        IoCancelIrp(DeviceExtension->DetectionIrp);
        DeviceExtension->DetectionSupported = FALSE;
    }
}

VOID
SerialMouseStopDetection(
    PDEVICE_EXTENSION DeviceExtension
    )
{
    PAGED_CODE();

    if (!DeviceExtension->DetectionSupported) {
        return;
    }

    if (!DeviceExtension->RemovalDetected) {
        IoCancelIrp(DeviceExtension->DetectionIrp);
    }
}

NTSTATUS
SerialMouseSerialMaskEventComplete (
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP           Irp,
    IN PVOID          Context
    )
{
    PDEVICE_EXTENSION       deviceExtension = (PDEVICE_EXTENSION) Context;
    PIO_WORKITEM            item;
    NTSTATUS                status;
    BOOLEAN                 killMouse = FALSE;

    //
    // DeviceObject is NULL b/c this driver was the one that allocated and sent
    // the irp, we must use deviceExtension->Self instead.
    //
    UNREFERENCED_PARAMETER(DeviceObject);

    if (!deviceExtension->Removed && !deviceExtension->SurpriseRemoved) {
        item = IoAllocateWorkItem(deviceExtension->Self);
        if (!item) {
            //
            // Well, we can't allocate the work item, so lets invalidate our device
            // state and hope everything gets torn down.
            //
            killMouse = TRUE;
        }
        else {
            status = IoAcquireRemoveLock(&deviceExtension->RemoveLock, item);

            if (NT_SUCCESS(status)) {
                IoQueueWorkItem (item,
                                 SerialMouseSerialMaskEventWorker,
                                 DelayedWorkQueue,
                                 item);
            }
            else {
                killMouse = TRUE;
            }
        }
    }

    if (killMouse) {
        deviceExtension->RemovalDetected = TRUE;
        IoInvalidateDeviceState(deviceExtension->PDO);
    }

    IoReleaseRemoveLock (&deviceExtension->RemoveLock,
                         deviceExtension->DetectionIrp);

    return STATUS_MORE_PROCESSING_REQUIRED;
}

VOID
SerialMouseSerialMaskEventWorker(
    PDEVICE_OBJECT DeviceObject,
    PIO_WORKITEM   Item
    )
{
    IO_STATUS_BLOCK     iosb;
    PIRP                irp;
    KEVENT              event;
    NTSTATUS            status;
    ULONG               bits;
    BOOLEAN             removeSelf = FALSE;
    PDEVICE_EXTENSION   deviceExtension = DeviceObject->DeviceExtension;

    PAGED_CODE();

    irp = deviceExtension->DetectionIrp;

    KeInitializeEvent(&event, NotificationEvent, FALSE);

    switch (irp->IoStatus.Status) {
    case STATUS_SUCCESS:

        Print(deviceExtension, DBG_SS_NOISE, 
              ("SerialEventBits are 0x%x\n", deviceExtension->SerialEventBits));

        bits = 0x0;

        status = SerialMouseIoSyncIoctlEx(IOCTL_SERIAL_GET_MODEMSTATUS,
                                          deviceExtension->TopOfStack, 
                                          &event,
                                          &iosb,
                                          NULL,
                                          0,
                                          &bits,
                                          sizeof(ULONG)
                                          );
    
        Print(deviceExtension, DBG_SS_NOISE, 
              ("get modem status, NTSTATUS = 0x%x, bits = 0x%x, MSB = 0x%x\n",
              status, bits, deviceExtension->ModemStatusBits));

        //
        // Make sure that the lines truly changed
        //
        if (deviceExtension->ModemStatusBits == bits) {
            //
            // Resend the detection irp
            //
            SerialMouseSendWaitMaskIrp(deviceExtension);
        }
        else {
            //
            // The lines have changed, it is a hot removal
            //
            Print(deviceExtension, DBG_SS_NOISE, ("device hot removed!\n"));
    
            SerialMouseIoSyncInternalIoctl(IOCTL_INTERNAL_SERENUM_REMOVE_SELF,
                                           deviceExtension->TopOfStack, 
                                           &event,
                                           &iosb);
        
            deviceExtension->RemovalDetected = TRUE;
        }

        break;

    case STATUS_CANCELLED:
        //
        // We get here if the user manually removes the device (ie through the 
        // device manager) and we send the clean up irp down the stack
        //
        Print(deviceExtension, DBG_SS_NOISE, ("wait cancelled!\n"));
        if (deviceExtension->PowerState != PowerDeviceD0 &&
            !deviceExtension->PoweringDown) {
            deviceExtension->RemovalDetected = TRUE;
        }

        break;

    default:
        Print(deviceExtension, DBG_SS_ERROR, 
              ("unknown status in mask event (0x%x)\n",
               irp->IoStatus.Status));
        TRAP();
    }

    IoReleaseRemoveLock(&deviceExtension->RemoveLock, Item);
    IoFreeWorkItem(Item);
}