/*++

Copyright (c) 2000  Microsoft Corporation

Module Name:

    ioctl.c

Abstract: Contains routines to support HIDCLASS internal
          ioctl queries for the pen tablet devices.

Environment:

    Kernel mode

Author:
    Michael Tsang (MikeTs) 13-Mar-2000

Revision History:

--*/

#include "pch.h"

#ifdef ALLOC_PRAGMA
  #pragma alloc_text(PAGE, GetDeviceDescriptor)
  #pragma alloc_text(PAGE, GetReportDescriptor)
  #pragma alloc_text(PAGE, GetString)
  #pragma alloc_text(PAGE, GetAttributes)
#endif

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @func   NTSTATUS | HpenInternalIoctl |
 *          Process the Control IRPs sent to this device.
 *          <nl>This function cannot be pageable because reads/writes
 *          can be made at dispatch-level
 *
 *  @parm   IN PDRIVER_OBJECT | DevObj | Points to the driver object.
 *  @parm   IN PIRP | Irp | Points to an I/O Request Packet.
 *
 *  @rvalue SUCCESS | returns STATUS_SUCCESS
 *  @rvalue FAILURE | returns NT status code
 *
 *****************************************************************************/

NTSTATUS EXTERNAL
HpenInternalIoctl(
    IN PDEVICE_OBJECT DevObj,
    IN PIRP           Irp
    )
{
    PROCNAME("HpenInternalIoctl")
    NTSTATUS status;
    PIO_STACK_LOCATION irpsp;
    PDEVICE_EXTENSION devext;

    irpsp = IoGetCurrentIrpStackLocation(Irp);

    ENTER(1, ("(DevObj=%p,Irp=%p,IrpSp=%p,Ioctl=%s)\n",
              DevObj, Irp, irpsp,
              LookupName(irpsp->Parameters.DeviceIoControl.IoControlCode,
                         HidIoctlNames)));

    Irp->IoStatus.Information = 0;
    devext = GET_MINIDRIVER_DEVICE_EXTENSION(DevObj);
    status = IoAcquireRemoveLock(&devext->RemoveLock, Irp);
    if (!NT_SUCCESS(status))
    {
        ERRPRINT(("received PnP IRP after device was removed\n"));
        Irp->IoStatus.Status = status;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
    }
    else
    {
        BOOLEAN fNeedCompletion = TRUE;

        ASSERT(devext->dwfHPen & HPENF_DEVICE_STARTED);
        switch(irpsp->Parameters.DeviceIoControl.IoControlCode)
        {
            case IOCTL_HID_GET_DEVICE_DESCRIPTOR:
                status = GetDeviceDescriptor(DevObj, Irp);
                break;

            case IOCTL_HID_GET_REPORT_DESCRIPTOR:
                status = GetReportDescriptor(DevObj, Irp);
                break;

            case IOCTL_HID_READ_REPORT:
                status = ReadReport(DevObj, Irp);
                fNeedCompletion = FALSE;
                break;

            case IOCTL_HID_GET_FEATURE:
                status = OemGetFeatures(DevObj, Irp);
                break;

            case IOCTL_HID_SET_FEATURE:
                status = OemSetFeatures(DevObj, Irp);
                break;

            case IOCTL_HID_GET_STRING:
                status = GetString(DevObj, Irp);
                break;

            case IOCTL_HID_GET_DEVICE_ATTRIBUTES:
                status = GetAttributes(DevObj, Irp);
                break;

            case IOCTL_HID_ACTIVATE_DEVICE:
            case IOCTL_HID_DEACTIVATE_DEVICE:
                status = STATUS_SUCCESS;
                break;

            default:
                WARNPRINT(("unsupported ioctl code (ioctl=%s)\n",
                           LookupName(irpsp->Parameters.DeviceIoControl.IoControlCode,
                                      HidIoctlNames)));
                status = Irp->IoStatus.Status;
                break;
        }

        if (status != STATUS_PENDING)
        {
            IoReleaseRemoveLock(&devext->RemoveLock, Irp);
            if (fNeedCompletion)
            {
                Irp->IoStatus.Status = status;
                IoCompleteRequest(Irp, IO_NO_INCREMENT);
            }
        }
        else
        {
            IoMarkIrpPending(Irp);
        }
    }

    EXIT(1, ("=%x\n", status));
    return status;
}       //HpenInternalIoctl

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   NTSTATUS | GetDeviceDescriptor |
 *          Respond to HIDCLASS IOCTL_HID_GET_DEVICE_DESCRIPTOR
 *          by returning a device descriptor.
 *
 *  @parm   IN PDRIVER_OBJECT | DevObj | Points to the driver object.
 *  @parm   IN PIRP | Irp | Points to an I/O Request Packet.
 *
 *  @rvalue SUCCESS | returns STATUS_SUCCESS
 *  @rvalue FAILURE | returns STATUS_BUFFER_TOO_SMALL - need more memory
 *
 *****************************************************************************/

NTSTATUS INTERNAL
GetDeviceDescriptor(
    IN PDEVICE_OBJECT DevObj,
    IN PIRP           Irp
    )
{
    PROCNAME("GetDeviceDescriptor")
    NTSTATUS status;
    PIO_STACK_LOCATION irpsp;

    PAGED_CODE ();

    irpsp = IoGetCurrentIrpStackLocation(Irp);

    ENTER(2, ("(DevObj=%p,Irp=%p,IrpSp=%p)\n", DevObj, Irp, irpsp));

    if (irpsp->Parameters.DeviceIoControl.OutputBufferLength <
        sizeof(gHidDescriptor))
    {
        ERRPRINT(("output buffer too small (bufflen=%d)\n",
                  irpsp->Parameters.DeviceIoControl.OutputBufferLength));
        status = STATUS_BUFFER_TOO_SMALL;
    }
    else
    {
        RtlCopyMemory(Irp->UserBuffer,
                      &gHidDescriptor,
                      sizeof(gHidDescriptor));

        Irp->IoStatus.Information = sizeof(gHidDescriptor);
        status = STATUS_SUCCESS;
    }

    EXIT(2, ("=%x\n", status));
    return status;
}       //GetDeviceDescriptor

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   NTSTATUS | GetReportDescriptor |
 *          Respond to HIDCLASS IOCTL_HID_GET_REPORT_DESCRIPTOR
 *          by returning appropriate the report descriptor.
 *
 *  @parm   IN PDRIVER_OBJECT | DevObj | Points to the driver object.
 *  @parm   IN PIRP | Irp | Points to an I/O Request Packet.
 *
 *  @rvalue SUCCESS | returns STATUS_SUCCESS
 *  @rvalue FAILURE | returns NT status code
 *
 *****************************************************************************/

NTSTATUS INTERNAL
GetReportDescriptor(
    IN PDEVICE_OBJECT DevObj,
    IN PIRP           Irp
    )
{
    PROCNAME("GetReportDescriptor")
    NTSTATUS status;
    PIO_STACK_LOCATION irpsp;

    PAGED_CODE ();

    irpsp = IoGetCurrentIrpStackLocation(Irp);

    ENTER(2, ("(DevObj=%p,Irp=%p,IrpSp=%p\n", DevObj, Irp, irpsp));

    if (irpsp->Parameters.DeviceIoControl.OutputBufferLength <
        sizeof(gReportDescriptor))
    {
        ERRPRINT(("output buffer too small (bufflen=%d)\n",
                  irpsp->Parameters.DeviceIoControl.OutputBufferLength));
        status = STATUS_BUFFER_TOO_SMALL;
    }
    else
    {
        RtlCopyMemory(Irp->UserBuffer,
                      gReportDescriptor,
                      sizeof(gReportDescriptor));

        Irp->IoStatus.Information = sizeof(gReportDescriptor);
        status = STATUS_SUCCESS;
    }

    EXIT(2, ("=%x\n", status));
    return status;
}       //GetReportDescriptor

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   NTSTATUS | ReadReport |
 *          Read input report.
 *
 *  @parm   IN PDRIVER_OBJECT | DevObj | Points to the driver object.
 *  @parm   IN PIRP | Irp | Points to an I/O Request Packet.
 *
 *  @rvalue SUCCESS | returns STATUS_SUCCESS
 *  @rvalue FAILURE | returns NT status code
 *
 *****************************************************************************/

NTSTATUS INTERNAL
ReadReport(
    IN PDEVICE_OBJECT DevObj,
    IN PIRP           Irp
    )
{
    PROCNAME("ReadReport")
    NTSTATUS status;
    PIO_STACK_LOCATION irpsp;
    PDEVICE_EXTENSION devext;
    ULONG DataLen;

    irpsp = IoGetCurrentIrpStackLocation(Irp);

    ENTER(2, ("(DevObj=%p,Irp=%p,IrpSp=%p)\n", DevObj, Irp, irpsp));

    ASSERT(Irp->UserBuffer != NULL);
    devext = GET_MINIDRIVER_DEVICE_EXTENSION(DevObj);
    DataLen = irpsp->Parameters.DeviceIoControl.OutputBufferLength;

    if (DataLen != sizeof(HID_INPUT_REPORT))
    {
        ERRPRINT(("invalid input report size (bufflen=%d)\n", DataLen));
        status = STATUS_INVALID_BUFFER_SIZE;
    }
    else if (!(devext->dwfHPen & HPENF_DEVICE_STARTED))
    {
        ERRPRINT(("device not started yet\n"));
        status = STATUS_DEVICE_NOT_READY ;
    }
    else
    {
        PHID_INPUT_REPORT HidReport = (PHID_INPUT_REPORT)Irp->UserBuffer;
        KIRQL OldIrql;

        KeAcquireSpinLock(&devext->SpinLock, &OldIrql);
        status = OemProcessResyncBuffer(devext, Irp);
        KeReleaseSpinLock(&devext->SpinLock, OldIrql);

        if (!NT_SUCCESS(status))
        {
            //
            // If we don't have enough bytes in the resync buffer or the packet
            // in the resync buffer is invalid, send an IRP down to read some
            // more.
            //
            status = SerialAsyncReadWritePort(TRUE,
                                              devext,
                                              Irp,
                                              HidReport->Report.RawInput,
                                              sizeof(OEM_INPUT_REPORT),
                                              ReadReportCompletion,
                                              HidReport);
        }
    }

    EXIT(2, ("=%x\n", status));
    return status;
}       //ReadReport

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   NTSTATUS | GetString |
 *          Respond to IOCTL_HID_GET_STRING.
 *
 *  @parm   IN PDRIVER_OBJECT | DevObj | Points to the driver object.
 *  @parm   IN PIRP | Irp | Points to an I/O Request Packet.
 *
 *  @rvalue SUCCESS | returns STATUS_SUCCESS
 *  @rvalue FAILURE | returns NT status code
 *
 *****************************************************************************/

NTSTATUS INTERNAL
GetString(
    PDEVICE_OBJECT DevObj,
    PIRP           Irp
    )
{
    PROCNAME("GetString")
    NTSTATUS status;
    PIO_STACK_LOCATION irpsp;
    PWSTR pwstrID;
    ULONG lenID;

    PAGED_CODE();

    irpsp = IoGetCurrentIrpStackLocation(Irp);

    ENTER(2, ("(DevObj=%p,Irp=%p,IrpSp=%p,StringID=%x)\n",
              DevObj, Irp, irpsp,
              (ULONG_PTR)irpsp->Parameters.DeviceIoControl.Type3InputBuffer));

    switch ((ULONG_PTR)irpsp->Parameters.DeviceIoControl.Type3InputBuffer &
            0xffff)
    {
        case HID_STRING_ID_IMANUFACTURER:
            pwstrID = gpwstrManufacturerID;
            break;

        case HID_STRING_ID_IPRODUCT:
            pwstrID = gpwstrProductID;
            break;

        case HID_STRING_ID_ISERIALNUMBER:
            pwstrID = gpwstrSerialNumber;
            break;

        default:
            pwstrID = NULL;
            break;
    }

    lenID = pwstrID? wcslen(pwstrID)*sizeof(WCHAR) + sizeof(UNICODE_NULL): 0;
    if (pwstrID == NULL)
    {
        ERRPRINT(("invalid string ID (ID=%x)\n",
                  (ULONG_PTR)irpsp->Parameters.DeviceIoControl.Type3InputBuffer));
        status = STATUS_INVALID_PARAMETER;
    }
    else if (irpsp->Parameters.DeviceIoControl.OutputBufferLength < lenID)
    {
        ERRPRINT(("output buffer too small (bufflen=%d,need=%d)\n",
                  irpsp->Parameters.DeviceIoControl.OutputBufferLength, lenID));
        status = STATUS_BUFFER_TOO_SMALL;
    }
    else
    {
        RtlCopyMemory(Irp->UserBuffer, pwstrID, lenID);

        Irp->IoStatus.Information = lenID;
        status = STATUS_SUCCESS;
    }

    EXIT(2, ("=%x (string=%S)\n", status, pwstrID? pwstrID: L"Null"));
    return status;
}       //GetString

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   NTSTATUS | GetAttributes |
 *          Respond to IOCTL_HID_GET_ATTRIBUTES, by filling
 *          the HID_DEVICE_ATTRIBUTES struct.
 *
 *  @parm   IN PDRIVER_OBJECT | DevObj | Points to the driver object.
 *  @parm   IN PIRP | Irp | Points to an I/O Request Packet.
 *
 *  @rvalue SUCCESS | returns STATUS_SUCCESS
 *  @rvalue FAILURE | returns NT status code
 *
 *****************************************************************************/

NTSTATUS INTERNAL
GetAttributes(
    PDEVICE_OBJECT DevObj,
    PIRP           Irp
    )
{
    PROCNAME("GetAttributes")
    NTSTATUS status;
    PIO_STACK_LOCATION irpsp;

    PAGED_CODE();

    irpsp = IoGetCurrentIrpStackLocation(Irp);

    ENTER(2, ("(DevObj=%p,Irp=%p,IrpSp=%p)\n", DevObj, Irp, irpsp));

    if (irpsp->Parameters.DeviceIoControl.OutputBufferLength <
        sizeof(HID_DEVICE_ATTRIBUTES))
    {
        ERRPRINT(("output buffer too small (bufflen=%d)\n",
                  irpsp->Parameters.DeviceIoControl.OutputBufferLength));
        status = STATUS_BUFFER_TOO_SMALL;
    }
    else
    {
        PDEVICE_EXTENSION devext;
        PHID_DEVICE_ATTRIBUTES DevAttrib;

        devext = GET_MINIDRIVER_DEVICE_EXTENSION(DevObj);
        DevAttrib = (PHID_DEVICE_ATTRIBUTES)Irp->UserBuffer;

        DevAttrib->Size = sizeof(HID_DEVICE_ATTRIBUTES);
        DevAttrib->VendorID = OEM_VENDOR_ID;
        DevAttrib->ProductID = devext->OemData.wProductID;
        DevAttrib->VersionNumber = devext->OemData.wFirmwareRev;

        Irp->IoStatus.Information = sizeof(HID_DEVICE_ATTRIBUTES);
        status = STATUS_SUCCESS;
    }

    EXIT(2, ("=%x\n", status));
    return status;
}       //GetAttributes

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   NTSTATUS | ReadReportCompletion | Completion routine for ReadReport.
 *
 *  @parm   IN PDEVICE_OBJECT | DevObj | Points to the device object.
 *  @parm   IN PIRP | Irp | Points to an I/O request packet.
 *  @parm   IN PHID_INPUT_REPORT | HidReport | Points to input data packet.
 *
 *  @rvalue SUCCESS | Returns STATUS_SUCCESS
 *  @rvalue FAILURE | Returns STATUS_MORE_PROCESSING_REQUIRED
 *
 *****************************************************************************/

NTSTATUS INTERNAL
ReadReportCompletion(
    IN PDEVICE_OBJECT    DevObj,
    IN PIRP              Irp,
    IN PHID_INPUT_REPORT HidReport
    )
{
    PROCNAME("ReadReportCompletion")
    NTSTATUS status = Irp->IoStatus.Status;
    PDEVICE_EXTENSION devext;

    ENTER(2, ("(DevObj=%p,Irp=%p,HidReport=%p,Status=%x)\n",
              DevObj, Irp, HidReport, status));

    devext = GET_MINIDRIVER_DEVICE_EXTENSION(DevObj);
    if (status == STATUS_CANCELLED)
    {
        WARNPRINT(("ReadReport IRP was cancelled\n"));
        status = STATUS_SUCCESS;
    }
    else if (!NT_SUCCESS(status))
    {
        ERRPRINT(("failed to read input data packet (status=%x)\n", status));
        status = STATUS_SUCCESS;
    }
    else
    {
        status = OemProcessInputData(devext, Irp, HidReport);
    }

    if (Irp->PendingReturned)
    {
        IoMarkIrpPending(Irp);
    }

    if (status != STATUS_MORE_PROCESSING_REQUIRED)
    {
        IoReleaseRemoveLock(&devext->RemoveLock, Irp);
    }

    EXIT(2, ("=%x\n", status));
    return status;
}       //ReadReportCompletion
