VOID VIOSerialPortWrite(IN WDFQUEUE Queue,
                        IN WDFREQUEST Request,
                        IN size_t Length)
{
    NTSTATUS status;
    PVOID InBuf;
    PVOID buffer;
    PVIOSERIAL_PORT Port;
    PWRITE_BUFFER_ENTRY entry;

    TraceEvents(TRACE_LEVEL_VERBOSE, DBG_WRITE,
        "--> %s Request: %p Length: %d\n", __FUNCTION__, Request, Length);

    PAGED_CODE();

    status = WdfRequestRetrieveInputBuffer(Request, Length, &InBuf, NULL);
    if (!NT_SUCCESS(status))
    {
        TraceEvents(TRACE_LEVEL_ERROR, DBG_WRITE,
            "Failed to retrieve input buffer: %x\n", status);
        WdfRequestComplete(Request, status);
        return;
    }

    Port = RawPdoSerialPortGetData(WdfIoQueueGetDevice(Queue))->port;

    if (VIOSerialWillWriteBlock(Port))
    {
        WdfRequestComplete(Request, STATUS_CANT_WAIT);
        return;
    }

    status = WdfRequestMarkCancelableEx(Request,
        VIOSerialPortWriteRequestCancel);

    if (!NT_SUCCESS(status))
    {
        TraceEvents(TRACE_LEVEL_ERROR, DBG_WRITE,
            "Failed to mark request as cancelable: %x\n", status);
        WdfRequestComplete(Request, status);
        return;
    }

    buffer = ExAllocatePoolWithTag(NonPagedPool, Length,
        VIOSERIAL_DRIVER_MEMORY_TAG);

    if (buffer == NULL)
    {
        TraceEvents(TRACE_LEVEL_ERROR, DBG_WRITE, "Failed to allocate.\n");
        WdfRequestComplete(Request, STATUS_INSUFFICIENT_RESOURCES);
        return;
    }

    entry = (PWRITE_BUFFER_ENTRY)ExAllocatePoolWithTag(NonPagedPool,
        sizeof(WRITE_BUFFER_ENTRY), VIOSERIAL_DRIVER_MEMORY_TAG);

    if (entry == NULL)
    {
        TraceEvents(TRACE_LEVEL_ERROR, DBG_WRITE,
            "Failed to allocate write buffer entry.\n");
        ExFreePoolWithTag(buffer, VIOSERIAL_DRIVER_MEMORY_TAG);
        WdfRequestComplete(Request, STATUS_INSUFFICIENT_RESOURCES);
        return;
    }

    RtlCopyMemory(buffer, InBuf, Length);
    WdfRequestSetInformation(Request, (ULONG_PTR)Length);

    entry->Buffer = buffer;
    PushEntryList(&Port->WriteBuffersList, &entry->ListEntry);

    Port->PendingWriteRequest = Request;

    if (VIOSerialSendBuffers(Port, buffer, Length) <= 0)
    {
        PSINGLE_LIST_ENTRY removed;

        TraceEvents(TRACE_LEVEL_ERROR, DBG_WRITE,
            "Failed to send user's buffer.\n");

        ExFreePoolWithTag(buffer, VIOSERIAL_DRIVER_MEMORY_TAG);
        
        removed = PopEntryList(&Port->WriteBuffersList);
        NT_ASSERT(entry == CONTAINING_RECORD(removed, WRITE_BUFFER_ENTRY, ListEntry));
        ExFreePoolWithTag(entry, VIOSERIAL_DRIVER_MEMORY_TAG);        
        
        Port->PendingWriteRequest = NULL;
        WdfRequestComplete(Request, STATUS_INSUFFICIENT_RESOURCES);
    }

    TraceEvents(TRACE_LEVEL_VERBOSE, DBG_WRITE,"<-- %s\n", __FUNCTION__);
}
VOID
VIOSerialPortRead(
    IN WDFQUEUE   Queue,
    IN WDFREQUEST Request,
    IN size_t     Length
    )
{
    PRAWPDO_VIOSERIAL_PORT  pdoData = RawPdoSerialPortGetData(WdfIoQueueGetDevice(Queue));
    size_t             length;
    NTSTATUS           status;
    PVOID              systemBuffer;
    BOOLEAN            nonBlock;

    TraceEvents(TRACE_LEVEL_INFORMATION, DBG_READ, "-->%s\n", __FUNCTION__);

    nonBlock = ((WdfFileObjectGetFlags(WdfRequestGetFileObject(Request)) & FO_SYNCHRONOUS_IO) != FO_SYNCHRONOUS_IO);

    status = WdfRequestRetrieveOutputBuffer(Request, Length, &systemBuffer, &length);
    if (!NT_SUCCESS(status))
    {
        WdfRequestComplete(Request, status);
        return;
    }

	WdfSpinLockAcquire(pdoData->port->InBufLock);

	if (!VIOSerialPortHasDataLocked(pdoData->port))
    {
        if (!pdoData->port->HostConnected)
        {
           WdfRequestComplete(Request, STATUS_INSUFFICIENT_RESOURCES);
        }
		else
		{
			ASSERT (pdoData->port->PendingReadRequest == NULL);
			status = WdfRequestMarkCancelableEx(Request,
				VIOSerialPortReadRequestCancel);
			if (!NT_SUCCESS(status))
			{
				WdfRequestComplete(Request, status);
			}
			else
			{
				pdoData->port->PendingReadRequest = Request;
			}
		}
    }
    else
    {
        length = (ULONG)VIOSerialFillReadBufLocked(pdoData->port, systemBuffer, length);
        if (length)
        {
           WdfRequestCompleteWithInformation(Request, status, (ULONG_PTR)length);
        }
        else
        {
           WdfRequestComplete(Request, STATUS_INSUFFICIENT_RESOURCES);
        }
    }
    WdfSpinLockRelease(pdoData->port->InBufLock);
    
	TraceEvents(TRACE_LEVEL_INFORMATION, DBG_READ,"<-- %s\n", __FUNCTION__);
    return;
}
VOID VIOSerialPortWrite(IN WDFQUEUE Queue,
                        IN WDFREQUEST Request,
                        IN size_t Length)
{
    NTSTATUS status;
    PVOID InBuf;
    PVOID buffer;
    PVIOSERIAL_PORT Port;
    PWRITE_BUFFER_ENTRY entry;
    WDFDEVICE Device;
    PDRIVER_CONTEXT Context;
    WDFMEMORY EntryHandle;

    TraceEvents(TRACE_LEVEL_VERBOSE, DBG_WRITE,
        "--> %s Request: %p Length: %d\n", __FUNCTION__, Request, Length);

    PAGED_CODE();

    Device = WdfIoQueueGetDevice(Queue);
    Port = RawPdoSerialPortGetData(Device)->port;
    if (Port->Removed)
    {
        TraceEvents(TRACE_LEVEL_WARNING, DBG_WRITE,
            "Write request on a removed port %d\n", Port->PortId);
        WdfRequestComplete(Request, STATUS_OBJECT_NO_LONGER_EXISTS);
        return;
    }

    status = WdfRequestRetrieveInputBuffer(Request, Length, &InBuf, NULL);
    if (!NT_SUCCESS(status))
    {
        TraceEvents(TRACE_LEVEL_ERROR, DBG_WRITE,
            "Failed to retrieve input buffer: %x\n", status);
        WdfRequestComplete(Request, status);
        return;
    }

    if (VIOSerialWillWriteBlock(Port))
    {
        WdfRequestComplete(Request, STATUS_CANT_WAIT);
        return;
    }

    buffer = ExAllocatePoolWithTag(NonPagedPool, Length,
        VIOSERIAL_DRIVER_MEMORY_TAG);

    if (buffer == NULL)
    {
        TraceEvents(TRACE_LEVEL_ERROR, DBG_WRITE, "Failed to allocate.\n");
        WdfRequestComplete(Request, STATUS_INSUFFICIENT_RESOURCES);
        return;
    }

    Context = GetDriverContext(WdfDeviceGetDriver(Device));
    status = WdfMemoryCreateFromLookaside(Context->WriteBufferLookaside, &EntryHandle);
    if (!NT_SUCCESS(status))
    {
        TraceEvents(TRACE_LEVEL_ERROR, DBG_WRITE,
            "Failed to allocate write buffer entry: %x.\n", status);
        ExFreePoolWithTag(buffer, VIOSERIAL_DRIVER_MEMORY_TAG);
        WdfRequestComplete(Request, STATUS_INSUFFICIENT_RESOURCES);
        return;
    }

    status = WdfRequestMarkCancelableEx(Request,
        VIOSerialPortWriteRequestCancel);

    if (!NT_SUCCESS(status))
    {
        TraceEvents(TRACE_LEVEL_ERROR, DBG_WRITE,
            "Failed to mark request as cancelable: %x\n", status);
        ExFreePoolWithTag(buffer, VIOSERIAL_DRIVER_MEMORY_TAG);
        WdfObjectDelete(EntryHandle);
        WdfRequestComplete(Request, status);
        return;
    }

    RtlCopyMemory(buffer, InBuf, Length);
    WdfRequestSetInformation(Request, (ULONG_PTR)Length);

    entry = (PWRITE_BUFFER_ENTRY)WdfMemoryGetBuffer(EntryHandle, NULL);
    entry->EntryHandle = EntryHandle;
    entry->Buffer = buffer;
    entry->Request = Request;

    if (VIOSerialSendBuffers(Port, entry, Length) <= 0)
    {
        TraceEvents(TRACE_LEVEL_ERROR, DBG_WRITE,
            "Failed to send user's buffer.\n");

        ExFreePoolWithTag(buffer, VIOSERIAL_DRIVER_MEMORY_TAG);
        WdfObjectDelete(EntryHandle);

        if (WdfRequestUnmarkCancelable(Request) != STATUS_CANCELLED)
        {
            WdfRequestComplete(Request, Port->Removed ?
                STATUS_INVALID_DEVICE_STATE : STATUS_INSUFFICIENT_RESOURCES);
        }
    }

    TraceEvents(TRACE_LEVEL_VERBOSE, DBG_WRITE,"<-- %s\n", __FUNCTION__);
}
VOID
VIOSerialPortRead(
    IN WDFQUEUE   Queue,
    IN WDFREQUEST Request,
    IN size_t     Length
    )
{
    PRAWPDO_VIOSERIAL_PORT  pdoData = RawPdoSerialPortGetData(WdfIoQueueGetDevice(Queue));
    PVIOSERIAL_PORT         pport = pdoData->port;
    size_t             length;
    NTSTATUS           status;
    PVOID              systemBuffer;

    TraceEvents(TRACE_LEVEL_INFORMATION, DBG_READ, "-->%s\n", __FUNCTION__);

    status = WdfRequestRetrieveOutputBuffer(Request, Length, &systemBuffer, &length);
    if (!NT_SUCCESS(status))
    {
        WdfRequestComplete(Request, status);
        return;
    }

	WdfSpinLockAcquire(pport->InBufLock);

	if (!VIOSerialPortHasDataLocked(pport))
	{
		if (!pport->HostConnected)
		{
			status = STATUS_INSUFFICIENT_RESOURCES;
			length = 0;
		}
		else
		{
			ASSERT (pport->PendingReadRequest == NULL);
			status = WdfRequestMarkCancelableEx(Request,
				VIOSerialPortReadRequestCancel);
			if (!NT_SUCCESS(status))
			{
				length = 0;
			}
			else
			{
				pport->PendingReadRequest = Request;
				Request = NULL;
			}
		}
    }
    else
    {
        length = (ULONG)VIOSerialFillReadBufLocked(pport, systemBuffer, length);
        if (!length)
        {
            status = STATUS_INSUFFICIENT_RESOURCES;
        }
    }

    WdfSpinLockRelease(pport->InBufLock);

    if (Request != NULL)
    {
        // we are completing the request right here, either because of
        // an error or because data was available in the input buffer
        WdfRequestCompleteWithInformation(Request, status, (ULONG_PTR)length);
    }

	TraceEvents(TRACE_LEVEL_INFORMATION, DBG_READ,"<-- %s\n", __FUNCTION__);
}
static VOID
XenUsb_EvtIoInternalDeviceControl_PVURB(
  WDFQUEUE queue,
  WDFREQUEST request,
  size_t output_buffer_length,
  size_t input_buffer_length,
  ULONG io_control_code)
{
  NTSTATUS status;
  WDFDEVICE device = WdfIoQueueGetDevice(queue);
  PXENUSB_DEVICE_DATA xudd = GetXudd(device);
  WDF_REQUEST_PARAMETERS wrp;
  pvurb_t *pvurb;
  partial_pvurb_t *partial_pvurb;
  KIRQL old_irql;
  
  UNREFERENCED_PARAMETER(input_buffer_length);
  UNREFERENCED_PARAMETER(output_buffer_length);
  UNREFERENCED_PARAMETER(io_control_code);

  FUNCTION_ENTER();

  ASSERT(io_control_code == IOCTL_INTERNAL_PVUSB_SUBMIT_URB);

  WDF_REQUEST_PARAMETERS_INIT(&wrp);
  WdfRequestGetParameters(request, &wrp);
  pvurb = (pvurb_t *)wrp.Parameters.Others.Arg1;
  ASSERT(pvurb);
  RtlZeroMemory(&pvurb->rsp, sizeof(pvurb->rsp));
  pvurb->status = STATUS_SUCCESS;
  pvurb->request = request;
  pvurb->ref = 1;
  pvurb->total_length = 0;
  partial_pvurb = ExAllocatePoolWithTag(NonPagedPool, sizeof(*partial_pvurb), XENUSB_POOL_TAG); /* todo - use lookaside */
  if (!partial_pvurb) {
    WdfRequestComplete(request, STATUS_INSUFFICIENT_RESOURCES);
    FUNCTION_EXIT();
    return;
  }
  KeAcquireSpinLock(&xudd->urb_ring_lock, &old_irql);
  status = WdfRequestMarkCancelableEx(request, XenUsb_EvtRequestCancelPvUrb);
  if (!NT_SUCCESS(status)) {
    KeReleaseSpinLock(&xudd->urb_ring_lock, old_irql);  
    FUNCTION_MSG("WdfRequestMarkCancelableEx returned %08x\n", status);
    WdfRequestComplete(request, STATUS_INSUFFICIENT_RESOURCES);
    FUNCTION_EXIT();
    return;
  }  
  
  partial_pvurb->req = pvurb->req;
  partial_pvurb->mdl = pvurb->mdl; /* 1:1 right now, but may need to split up large pvurb into smaller partial_pvurb's */
  partial_pvurb->pvurb = pvurb;
  partial_pvurb->other_partial_pvurb = NULL;
  partial_pvurb->on_ring = FALSE;
  if (!partial_pvurb->mdl) {
    partial_pvurb->req.nr_buffer_segs = 0;
    partial_pvurb->req.buffer_length = 0;
  } else {
    ULONG remaining = MmGetMdlByteCount(partial_pvurb->mdl);
    USHORT offset = (USHORT)MmGetMdlByteOffset(partial_pvurb->mdl);
    int i;
    partial_pvurb->req.buffer_length = (USHORT)MmGetMdlByteCount(partial_pvurb->mdl);
    partial_pvurb->req.nr_buffer_segs = (USHORT)ADDRESS_AND_SIZE_TO_SPAN_PAGES(MmGetMdlVirtualAddress(partial_pvurb->mdl), MmGetMdlByteCount(partial_pvurb->mdl));
    for (i = 0; i < partial_pvurb->req.nr_buffer_segs; i++) {
      partial_pvurb->req.seg[i].gref = XnGrantAccess(xudd->handle, (ULONG)MmGetMdlPfnArray(partial_pvurb->mdl)[i], FALSE, INVALID_GRANT_REF, (ULONG)'XUSB');
      partial_pvurb->req.seg[i].offset = (USHORT)offset;
      partial_pvurb->req.seg[i].length = (USHORT)min((USHORT)remaining, (USHORT)PAGE_SIZE - offset);
      offset = 0;
      remaining -= partial_pvurb->req.seg[i].length;
      FUNCTION_MSG("seg = %d\n", i);
      FUNCTION_MSG(" gref = %d\n", partial_pvurb->req.seg[i].gref);
      FUNCTION_MSG(" offset = %d\n", partial_pvurb->req.seg[i].offset);
      FUNCTION_MSG(" length = %d\n", partial_pvurb->req.seg[i].length);
    }
    FUNCTION_MSG("buffer_length = %d\n", partial_pvurb->req.buffer_length);
    FUNCTION_MSG("nr_buffer_segs = %d\n", partial_pvurb->req.nr_buffer_segs);
  }
  InsertTailList(&xudd->partial_pvurb_queue, &partial_pvurb->entry);
  PutRequestsOnRing(xudd);
  KeReleaseSpinLock(&xudd->urb_ring_lock, old_irql);  
  
  FUNCTION_EXIT();
}
NTSTATUS
NfcCxSCPresentAbsentDispatcherSetRequest(
    _In_ PNFCCX_FDO_CONTEXT FdoContext,
    _In_ PNFCCX_SC_PRESENT_ABSENT_DISPATCHER Dispatcher,
    _In_ WDFREQUEST Request
    )
/*++

Routine Description:

    Stores a request in the dispatcher. If a request is already pending in the dispatcher, the new request will
    be completed with STATUS_DEVICE_BUSY.

Arguments:

    FdoContext - Pointer to the FDO Context
    Dispatcher - Pointer to the Dispatcher.
    Request - The request to store in the dispatcher.

Return Value:

    NTSTATUS

--*/
{
    NTSTATUS status;
    bool powerReferenceAcquired = false;
    bool cancelCallbackSet = false;

    TRACE_FUNCTION_ENTRY(LEVEL_VERBOSE);

    WDFFILEOBJECT fileObject = WdfRequestGetFileObject(Request);
    PNFCCX_FILE_CONTEXT fileContext = NfcCxFileGetContext(fileObject);

    // Pre-check if there is a current request.
    if (ReadPointerAcquire((void**)&Dispatcher->CurrentRequest) != nullptr)
    {
        status = STATUS_DEVICE_BUSY;
        TRACE_LINE(LEVEL_ERROR, "An Is Present/Absent request is already pending. %!STATUS!", status);
        goto Done;
    }

    // Allocate and initialize the request context
    PNFCCX_SC_REQUEST_CONTEXT requestContext = nullptr;

    WDF_OBJECT_ATTRIBUTES contextAttributes;
    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&contextAttributes, NFCCX_SC_REQUEST_CONTEXT);

    status = WdfObjectAllocateContext(Request, &contextAttributes, (void**)&requestContext);
    if (!NT_SUCCESS(status))
    {
        TRACE_LINE(LEVEL_ERROR, "Failed to set request's context. %!STATUS!", status);
        goto Done;
    }

    requestContext->Dispatcher = Dispatcher;

    // Add a power reference (if required).
    if (Dispatcher->PowerManaged)
    {
        status = NfcCxPowerFileAddReference(FdoContext->Power, fileContext, NfcCxPowerReferenceType_Proximity);
        if (!NT_SUCCESS(status))
        {
            TRACE_LINE(LEVEL_VERBOSE, "Failed to acquire power reference. %!STATUS!", status);
            goto Done;
        }

        powerReferenceAcquired = true;
    }

    // Setup cancel callback.
    status = WdfRequestMarkCancelableEx(Request, NfcCxSCPresentAbsentDispatcherRequestCanceled);
    if (!NT_SUCCESS(status))
    {
        TRACE_LINE(LEVEL_ERROR, "Failed to set request canceled callback. %!STATUS!", status);
        goto Done;
    }

    // Add a ref-count to the request, which is owned by the cancel callback.
    WdfObjectReference(Request);
    cancelCallbackSet = true;

    // Try to set the current request.
    void* previousRequest = InterlockedCompareExchangePointer((void**)&Dispatcher->CurrentRequest, /*exchange*/ Request, /*compare*/ nullptr);

    // Check if we already have a pending request.
    if (previousRequest != nullptr)
    {
        status = STATUS_DEVICE_BUSY;
        TRACE_LINE(LEVEL_ERROR, "An Is Present/Absent request is already pending. %!STATUS!", status);
        goto Done;
    }

Done:
    if (!NT_SUCCESS(status))
    {
        if (cancelCallbackSet)
        {
            NTSTATUS unmarkStatus = WdfRequestUnmarkCancelable(Request);
            if (unmarkStatus != STATUS_CANCELLED)
            {
                // Cancel callback will not be called.
                // So release cancel callback's request ref-count.
                WdfObjectDereference(Request);
            }
        }

        if (powerReferenceAcquired)
        {
            NfcCxPowerFileRemoveReference(FdoContext->Power, fileContext, NfcCxPowerReferenceType_Proximity);
        }
    }

    TRACE_FUNCTION_EXIT_NTSTATUS(LEVEL_VERBOSE, status);
    return status;
}