/*++
RioGamepad — device creation, VHF setup, and the report-submit IOCTL.

The driver is a thin relay: it creates a virtual HID device via VHF using the
report descriptor in ReportDescriptor.h, exposes a device interface, and on each
IOCTL_RIO_SUBMIT_REPORT forwards the caller's report bytes to the HID stack with
VhfReadReportSubmit. All report packing lives in the user-mode client.
--*/

#include <ntddk.h>
#include <initguid.h>   // makes DEFINE_GUID in Public.h allocate the GUID storage
#include "Driver.h"
#include "ReportDescriptor.h"

NTSTATUS
RioGamepadEvtDeviceAdd(
    _In_    WDFDRIVER       Driver,
    _Inout_ PWDFDEVICE_INIT DeviceInit
    )
{
    NTSTATUS status;
    WDFDEVICE device;
    WDF_OBJECT_ATTRIBUTES deviceAttributes;
    WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks;
    WDF_IO_QUEUE_CONFIG queueConfig;
    PDEVICE_CONTEXT deviceContext;
    VHF_CONFIG vhfConfig;

    UNREFERENCED_PARAMETER(Driver);

    WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
    pnpPowerCallbacks.EvtDeviceD0Entry = RioGamepadEvtDeviceD0Entry;
    WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks);

    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DEVICE_CONTEXT);
    deviceAttributes.EvtCleanupCallback = RioGamepadEvtDeviceCleanup;

    status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);
    if (!NT_SUCCESS(status))
    {
        return status;
    }

    deviceContext = DeviceGetContext(device);
    deviceContext->VhfHandle = NULL;

    status = WdfDeviceCreateDeviceInterface(device, &GUID_DEVINTERFACE_RIOGAMEPAD, NULL);
    if (!NT_SUCCESS(status))
    {
        return status;
    }

    //
    // Default parallel queue for the report-submit IOCTL.
    //
    WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queueConfig, WdfIoQueueDispatchParallel);
    queueConfig.EvtIoDeviceControl = RioGamepadEvtIoDeviceControl;

    status = WdfIoQueueCreate(device, &queueConfig, WDF_NO_OBJECT_ATTRIBUTES, NULL);
    if (!NT_SUCCESS(status))
    {
        return status;
    }

    //
    // Create the virtual HID device. VhfStart is deferred to D0Entry.
    //
    VHF_CONFIG_INIT(
        &vhfConfig,
        WdfDeviceWdmGetDeviceObject(device),
        (USHORT)sizeof(g_RioReportDescriptor),
        (PUCHAR)g_RioReportDescriptor);

    vhfConfig.VendorID = RIO_VENDOR_ID;
    vhfConfig.ProductID = RIO_PRODUCT_ID;
    vhfConfig.VersionNumber = RIO_VERSION;

    status = VhfCreate(&vhfConfig, &deviceContext->VhfHandle);
    if (!NT_SUCCESS(status))
    {
        deviceContext->VhfHandle = NULL;
        return status;
    }

    return STATUS_SUCCESS;
}

NTSTATUS
RioGamepadEvtDeviceD0Entry(
    _In_ WDFDEVICE              Device,
    _In_ WDF_POWER_DEVICE_STATE PreviousState
    )
{
    PDEVICE_CONTEXT deviceContext = DeviceGetContext(Device);

    UNREFERENCED_PARAMETER(PreviousState);

    return VhfStart(deviceContext->VhfHandle);
}

VOID
RioGamepadEvtDeviceCleanup(
    _In_ WDFOBJECT Object
    )
{
    PDEVICE_CONTEXT deviceContext = DeviceGetContext((WDFDEVICE)Object);

    if (deviceContext->VhfHandle != NULL)
    {
        VhfDelete(deviceContext->VhfHandle, TRUE);
        deviceContext->VhfHandle = NULL;
    }
}

VOID
RioGamepadEvtIoDeviceControl(
    _In_ WDFQUEUE   Queue,
    _In_ WDFREQUEST Request,
    _In_ size_t     OutputBufferLength,
    _In_ size_t     InputBufferLength,
    _In_ ULONG      IoControlCode
    )
{
    NTSTATUS status = STATUS_INVALID_DEVICE_REQUEST;
    PDEVICE_CONTEXT deviceContext = DeviceGetContext(WdfIoQueueGetDevice(Queue));

    UNREFERENCED_PARAMETER(OutputBufferLength);

    if (IoControlCode == IOCTL_RIO_SUBMIT_REPORT)
    {
        if (InputBufferLength != RIO_REPORT_SIZE)
        {
            status = STATUS_INVALID_BUFFER_SIZE;
        }
        else
        {
            PVOID buffer;
            size_t bufferSize;

            status = WdfRequestRetrieveInputBuffer(Request, RIO_REPORT_SIZE, &buffer, &bufferSize);
            if (NT_SUCCESS(status))
            {
                HID_XFER_PACKET packet;
                packet.reportId = 0;
                packet.reportBuffer = (PUCHAR)buffer;
                packet.reportBufferLen = RIO_REPORT_SIZE;

                status = VhfReadReportSubmit(deviceContext->VhfHandle, &packet);
            }
        }
    }

    WdfRequestComplete(Request, status);
}
