#include "precomp.h"

#if defined(EVENT_TRACING)
#include "queue.tmh"
#endif

#ifdef USE_BALLOON_SERVICE

EVT_WDF_IO_QUEUE_IO_WRITE BalloonIoWrite;
EVT_WDF_IO_QUEUE_IO_STOP BalloonIoStop;
EVT_WDF_REQUEST_CANCEL BalloonEvtRequestCancel;

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, BalloonQueueInitialize)
#endif

NTSTATUS
BalloonQueueInitialize(IN WDFDEVICE Device)
{
    NTSTATUS status = STATUS_SUCCESS;
    WDF_IO_QUEUE_CONFIG queueConfig = {0};

    PAGED_CODE();

    WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queueConfig, WdfIoQueueDispatchSequential);
    queueConfig.EvtIoWrite = BalloonIoWrite;
    queueConfig.EvtIoStop = BalloonIoStop;

    status = WdfIoQueueCreate(Device, &queueConfig, WDF_NO_OBJECT_ATTRIBUTES, WDF_NO_HANDLE);

    if (!NT_SUCCESS(status))
    {
        TraceEvents(TRACE_LEVEL_ERROR, DBG_PNP, "WdfIoQueueCreate failed 0x%08x\n", status);
    }
    return status;
}

VOID BalloonIoWrite(IN WDFQUEUE Queue, IN WDFREQUEST Request, IN size_t Length)
{
    PVOID buffer = NULL;
    size_t buffSize;
    PDEVICE_CONTEXT devCtx = NULL;
    NTSTATUS status = STATUS_SUCCESS;

    TraceEvents(TRACE_LEVEL_INFORMATION,
                DBG_HW_ACCESS,
                "BalloonIoWrite Called! Queue 0x%p, Request 0x%p Length %d\n",
                Queue,
                Request,
                Length);

    devCtx = GetDeviceContext(WdfIoQueueGetDevice(Queue));

    if (Length < sizeof(BALLOON_STAT))
    {
        TraceEvents(TRACE_LEVEL_INFORMATION,
                    DBG_HW_ACCESS,
                    "BalloonIoWrite Buffer Length too small %d, expected is %d\n",
                    Length,
                    sizeof(BALLOON_STAT));
        WdfRequestComplete(Request, STATUS_BUFFER_TOO_SMALL);
        return;
    }

    status = WdfRequestRetrieveInputBuffer(Request, sizeof(BALLOON_STAT), &buffer, &buffSize);
    if (!NT_SUCCESS(status) || (buffer == NULL))
    {
        TraceEvents(TRACE_LEVEL_INFORMATION,
                    DBG_HW_ACCESS,
                    "BalloonIoWrite Could not get request memory buffer 0x%08x\n",
                    status);
        WdfRequestCompleteWithInformation(Request, status, 0L);
        return;
    }

    if (devCtx->HandleWriteRequest)
    {
        Length = min(buffSize, sizeof(BALLOON_STAT) * VIRTIO_BALLOON_S_NR);
#if 0
        {
            size_t i;
            PBALLOON_STAT stat = (PBALLOON_STAT)buffer;
            for (i = 0; i < Length / sizeof(BALLOON_STAT); ++i)
            {
                TraceEvents(TRACE_LEVEL_INFORMATION, DBG_HW_ACCESS,
                    "tag = %d, val = %08I64X\n\n", stat[i].tag, stat[i].val);
            }
        }
#endif
        RtlCopyMemory(devCtx->MemStats, buffer, Length);
        WdfRequestCompleteWithInformation(Request, status, Length);
        TraceEvents(TRACE_LEVEL_INFORMATION,
                    DBG_HW_ACCESS,
                    "WdfRequestCompleteWithInformation Called! Queue 0x%p, Request 0x%p Length %d Status 0x%08x\n",
                    Queue,
                    Request,
                    Length,
                    status);

        devCtx->HandleWriteRequest = FALSE;
        BalloonMemStats(WdfIoQueueGetDevice(Queue));
    }
    else
    {
        status = WdfRequestMarkCancelableEx(Request, BalloonEvtRequestCancel);
        if (!NT_SUCCESS(status))
        {
            TraceEvents(TRACE_LEVEL_ERROR, DBG_READ, "WdfRequestMarkCancelableEx failed: 0x%08x\n", status);
            WdfRequestCompleteWithInformation(Request, status, 0L);
            return;
        }

        devCtx->PendingWriteRequest = Request;
    }
}

VOID BalloonIoStop(IN WDFQUEUE Queue, IN WDFREQUEST Request, IN ULONG ActionFlags)
{
    UNREFERENCED_PARAMETER(Queue);

    TraceEvents(TRACE_LEVEL_VERBOSE, DBG_READ, "--> %!FUNC! Request: %p", Request);

    if (ActionFlags & WdfRequestStopRequestCancelable)
    {
        NTSTATUS status = WdfRequestUnmarkCancelable(Request);
        __analysis_assume(status != STATUS_NOT_SUPPORTED);
        if (status == STATUS_CANCELLED)
        {
            WdfRequestStopAcknowledge(Request, FALSE);
            goto end_io_stop;
        }
    }

    if (ActionFlags & WdfRequestStopActionSuspend)
    {
        WdfRequestStopAcknowledge(Request, TRUE);
    }
    else if (ActionFlags & WdfRequestStopActionPurge)
    {
        WdfRequestComplete(Request, STATUS_CANCELLED);
    }
    else
    {
        WdfRequestComplete(Request, STATUS_UNSUCCESSFUL);
    }

end_io_stop:
    TraceEvents(TRACE_LEVEL_VERBOSE, DBG_READ, "<-- %!FUNC!");
}

VOID BalloonEvtRequestCancel(IN WDFREQUEST Request)
{
    PDEVICE_CONTEXT devCtx = GetDeviceContext(WdfIoQueueGetDevice(WdfRequestGetIoQueue(Request)));

    TraceEvents(TRACE_LEVEL_VERBOSE, DBG_READ, "--> BalloonEvtRequestCancel Cancelled Request: %p\n", Request);

    NT_ASSERT(devCtx->PendingWriteRequest == Request);
    devCtx->PendingWriteRequest = NULL;
    WdfRequestComplete(Request, STATUS_CANCELLED);

    TraceEvents(TRACE_LEVEL_VERBOSE, DBG_READ, "<-- BalloonEvtRequestCancel\n");
}

#endif // USE_BALLOON_SERVICE
