/*++

Copyright (c) 1989-1993  Microsoft Corporation

Module Name:

    receive.c

Abstract:

    This module contains code which performs the following TDI services:

        o   TdiReceiveDatagram

Environment:

    Kernel mode

Revision History:

--*/

#include "precomp.h"
#pragma hdrstop



VOID
IpxTransferDataComplete(
    IN NDIS_HANDLE BindingContext,
    IN PNDIS_PACKET NdisPacket,
    IN NDIS_STATUS NdisStatus,
    IN UINT BytesTransferred
    )

/*++

Routine Description:

    This routine receives control from the physical provider as an
    indication that an NdisTransferData has completed. We use this indication
    to complete any pended requests to our clients.

Arguments:

    BindingContext - The Adapter Binding specified at initialization time.

    NdisPacket/RequestHandle - An identifier for the request that completed.

    NdisStatus - The completion status for the request.

    BytesTransferred - Number of bytes actually transferred.


Return Value:

    None.

--*/

{
    PADAPTER Adapter = (PADAPTER)BindingContext;
    PIPX_RECEIVE_RESERVED Reserved = (PIPX_RECEIVE_RESERVED)(NdisPacket->ProtocolReserved);
    PREQUEST Request, LastRequest;
    PADDRESS_FILE AddressFile;
    ULONG ByteOffset;
    PLIST_ENTRY p;
    PDEVICE Device;


    switch (Reserved->Identifier) {

    case IDENTIFIER_IPX:

        if (!Reserved->pContext) {

            if (Reserved->SingleRequest) {

                //
                // The transfer was directly into the client buffer,
                // so simply complete the request.
                //

                Request = Reserved->SingleRequest;

                if (NdisStatus == NDIS_STATUS_SUCCESS) {

                    IPX_DEBUG (RECEIVE, ("Transferred %d bytes\n", BytesTransferred));
                    REQUEST_INFORMATION(Request) = BytesTransferred;
                    REQUEST_STATUS(Request) = STATUS_SUCCESS;

                } else {

                    IPX_DEBUG (RECEIVE, ("Transfer failed\n"));
                    REQUEST_INFORMATION(Request) = 0;
                    REQUEST_STATUS(Request) = STATUS_ADAPTER_HARDWARE_ERROR;

                }

                LastRequest = Request;
                Reserved->SingleRequest = NULL;

            } else {

                //
                // Multiple clients requested this datagram. Save
                // the last one to delay queueing it for completion.
                //

                LastRequest = LIST_ENTRY_TO_REQUEST (Reserved->Requests.Blink);

                while (TRUE) {

                    p = RemoveHeadList (&Reserved->Requests);
                    if (p == &Reserved->Requests) {
                        break;
                    }

                    Request = LIST_ENTRY_TO_REQUEST(p);
                    AddressFile = REQUEST_OPEN_CONTEXT(Request);

                    if (AddressFile->ReceiveIpxHeader) {
                        ByteOffset = 0;
                    } else {
                        ByteOffset = sizeof(IPX_HEADER);
                    }

                    if (NdisStatus == NDIS_STATUS_SUCCESS) {
                       UINT BytesToTransfer = ((PTDI_REQUEST_KERNEL_RECEIVEDG)(REQUEST_PARAMETERS(Request)))->ReceiveLength; 

		       if (BytesToTransfer == 0) {
			  BytesToTransfer= IpxGetChainedMDLLength(REQUEST_NDIS_BUFFER(Request)); 
		       }
#ifdef SUNDOWN
		       // assume offset will not exceed 2^32. 
		       // REQUEST_INFORMATION(Request) is a ULONG_PTR
		       // we are save to cast its address to PULONG.
		       
		       REQUEST_STATUS(Request) =
			  TdiCopyBufferToMdl(
					     Reserved->ReceiveBuffer->Data,
					     (ULONG) (ByteOffset + REQUEST_INFORMATION(Request)),
					     BytesToTransfer,
					     REQUEST_NDIS_BUFFER(Request),
					     0,
					     (PULONG) &REQUEST_INFORMATION(Request));

#else

		       REQUEST_STATUS(Request) =
			   TdiCopyBufferToMdl(
					   Reserved->ReceiveBuffer->Data,
					   ByteOffset + REQUEST_INFORMATION(Request),
					   BytesToTransfer,
					   REQUEST_NDIS_BUFFER(Request),
					   0,
					   &REQUEST_INFORMATION(Request));

#endif

                    } else {

                        REQUEST_INFORMATION(Request) = 0;
                        REQUEST_STATUS(Request) = STATUS_ADAPTER_HARDWARE_ERROR;

                    }

                    if (Request != LastRequest) {

                        IPX_INSERT_TAIL_LIST(
                            &Adapter->RequestCompletionQueue,
                            REQUEST_LINKAGE(Request),
                            Adapter->DeviceLock);

                    }

                }

                //
                // Now free the receive buffer back.
                //

                IPX_PUSH_ENTRY_LIST(
                    &Adapter->ReceiveBufferList,
                    &Reserved->ReceiveBuffer->PoolLinkage,
                    &Adapter->Device->SListsLock);

                Reserved->ReceiveBuffer = NULL;

            }

        } else {
           //IpxPrint0("IpxTransferDataComplete: Calling PassDgToRt\n");
           //ByteOffset = sizeof(IPX_HEADER);
           ByteOffset = 0;
           PassDgToRt(IpxDevice, Reserved->pContext, Reserved->Index,
                   &Reserved->ReceiveBuffer->Data[ByteOffset],
                                 BytesTransferred);

           //
           // Free the memory allocated for options.
           //
           IpxFreeMemory(Reserved->pContext, sizeof(IPX_DATAGRAM_OPTIONS2),
                   MEMORY_PACKET, "RT OPTIONS");
           //
           // Now free the receive buffer back.
           //

           IPX_PUSH_ENTRY_LIST(
            &Adapter->ReceiveBufferList,
            &Reserved->ReceiveBuffer->PoolLinkage,
            Adapter->DeviceLock);

           Reserved->ReceiveBuffer = NULL;
        }

        //
        // Now free the packet.
        //

        NdisReinitializePacket (NdisPacket);

        if (Reserved->OwnedByAddress) {

            // Reserved->Address->ReceivePacketInUse = FALSE;
            InterlockedDecrement(&Reserved->Address->ReceivePacketInUse);

        } else {

            Device = Adapter->Device;

            IPX_PUSH_ENTRY_LIST(
                &Device->ReceivePacketList,
                &Reserved->PoolLinkage,
                &Device->SListsLock);

        }

        if (!Reserved->pContext) {
            //
            // We Delay inserting the last request (or the only one)
            // until after we have put the packet back, to keep the
            // address around if needed (the address won't go away
            // until the last address file does, and the address file
            // won't go away until the datagram is completed).
            //

            IPX_INSERT_TAIL_LIST(
                &Adapter->RequestCompletionQueue,
                REQUEST_LINKAGE(LastRequest),
                Adapter->DeviceLock);
        }

        IpxReceiveComplete ((NDIS_HANDLE)Adapter);

        break;

    default:

        Device = Adapter->Device;

        (*Device->UpperDrivers[Reserved->Identifier].TransferDataCompleteHandler)(
            NdisPacket,
            NdisStatus,
            BytesTransferred);

        break;

    }

}   /* IpxTransferDataComplete */


VOID
IpxTransferData(
	OUT PNDIS_STATUS Status,
	IN NDIS_HANDLE NdisBindingHandle,
	IN NDIS_HANDLE MacReceiveContext,
	IN UINT ByteOffset,
	IN UINT BytesToTransfer,
	IN OUT PNDIS_PACKET Packet,
	OUT PUINT BytesTransferred
    )

/*++

Routine Description:

    This routine is called by all tightly bound clients instead of NdisTransferData.
    If this is a loopback packet, the transfer is done directly here, else NdisTransferData
    is called.

Arguments:

    Status - status of operation
	NdisBindingHandle - Loopback cookie or Ndis context
	MacReceiveContext - Loopback packet or Mac context
	ByteOffset - Source offset
	BytesToTransfer - length of the transfer desired
	Packet - dest packet
	BytesTransferred - length of successful transfer

Return Value:

    NTSTATUS - status of operation.

--*/

{
    //
    // If this is a loopback packet, copy the data directly
    //
    if (NdisBindingHandle == (PVOID)IPX_LOOPBACK_COOKIE) {

        IPX_DEBUG (LOOPB, ("LoopbXfer: src: %lx, dest: %lx, bytestoxfer: %lx\n",
                        MacReceiveContext, Packet, BytesToTransfer));

        NdisCopyFromPacketToPacketSafe(
            Packet,             // Destination
            0,                  // DestinationOffset
            BytesToTransfer,    // BytesToCopy
            (PNDIS_PACKET)MacReceiveContext,        // Source
            ByteOffset,  // SourceOffset
            BytesTransferred,   // BytesCopied
            NormalPagePriority);

        *Status = ((*BytesTransferred == BytesToTransfer)? NDIS_STATUS_SUCCESS : NDIS_STATUS_RESOURCES);
    } else {
        NdisTransferData(
            Status,
            NdisBindingHandle,
            MacReceiveContext,
            ByteOffset,
            BytesToTransfer,
            Packet,
            BytesTransferred);
    }
}



NTSTATUS
IpxTdiReceiveDatagram(
    IN PREQUEST Request
    )

/*++

Routine Description:

    This routine performs the TdiReceiveDatagram request for the transport
    provider. Receive datagrams just get queued up to an address, and are
    completed when a DATAGRAM or DATAGRAM_BROADCAST frame is received at
    the address.

Arguments:

    Irp - I/O Request Packet for this request.

Return Value:

    NTSTATUS - status of operation.

--*/

{

    PADDRESS Address;
    PADDRESS_FILE AddressFile;
    IPX_DEFINE_SYNC_CONTEXT (SyncContext)
    IPX_DEFINE_LOCK_HANDLE (LockHandle)


    //
    // Do a quick check of the validity of the address.
    //

    AddressFile = (PADDRESS_FILE)REQUEST_OPEN_CONTEXT(Request);

    if ((AddressFile->Size != sizeof (ADDRESS_FILE)) ||
        (AddressFile->Type != IPX_ADDRESSFILE_SIGNATURE)) {

        return STATUS_INVALID_HANDLE;
    }

    Address = AddressFile->Address;

    if ((Address == NULL) ||
        (Address->Size != sizeof (ADDRESS)) ||
        (Address->Type != IPX_ADDRESS_SIGNATURE)) {

        return STATUS_INVALID_HANDLE;
    }

    IPX_BEGIN_SYNC (&SyncContext);

    IPX_GET_LOCK (&Address->Lock, &LockHandle);

    if (AddressFile->State != ADDRESSFILE_STATE_OPEN) {

        IPX_FREE_LOCK (&Address->Lock, LockHandle);
        IPX_END_SYNC (&SyncContext);
        return STATUS_INVALID_HANDLE;
    }


    InsertTailList (&AddressFile->ReceiveDatagramQueue, REQUEST_LINKAGE(Request));

    IoSetCancelRoutine (Request, IpxCancelReceiveDatagram);

    if (Request->Cancel) {

        (VOID)RemoveTailList (&AddressFile->ReceiveDatagramQueue);
        IoSetCancelRoutine (Request, (PDRIVER_CANCEL)NULL);
        IPX_FREE_LOCK (&Address->Lock, LockHandle);
        IPX_END_SYNC (&SyncContext);
        return STATUS_CANCELLED;
    }

    IPX_DEBUG (RECEIVE, ("RDG posted on %lx\n", AddressFile));

    IpxReferenceAddressFileLock (AddressFile, AFREF_RCV_DGRAM);

    IPX_FREE_LOCK (&Address->Lock, LockHandle);

    IPX_END_SYNC (&SyncContext);

    return STATUS_PENDING;

}   /* IpxTdiReceiveDatagram */


VOID
IpxCancelReceiveDatagram(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )

/*++

Routine Description:

    This routine is called by the I/O system to cancel a receive
    datagram. The datagram is found on the address file's receive
    datagram queue.

    NOTE: This routine is called with the CancelSpinLock held and
    is responsible for releasing it.

Arguments:

    DeviceObject - Pointer to the device object for this driver.

    Irp - Pointer to the request packet representing the I/O request.

Return Value:

    none.

--*/

{

    PLIST_ENTRY p;
    PADDRESS_FILE AddressFile;
    PADDRESS Address;
    PREQUEST Request = (PREQUEST)Irp;
    BOOLEAN Found;
    IPX_DEFINE_LOCK_HANDLE (LockHandle)


    CTEAssert ((REQUEST_MAJOR_FUNCTION(Request) == IRP_MJ_INTERNAL_DEVICE_CONTROL) &&
               (REQUEST_MINOR_FUNCTION(Request) == TDI_RECEIVE_DATAGRAM));

    CTEAssert (REQUEST_OPEN_TYPE(Request) == (PVOID)TDI_TRANSPORT_ADDRESS_FILE);

    AddressFile = (PADDRESS_FILE)REQUEST_OPEN_CONTEXT(Request);
    Address = AddressFile->Address;

    Found = FALSE;

    IPX_GET_LOCK (&Address->Lock, &LockHandle);

    for (p = AddressFile->ReceiveDatagramQueue.Flink;
         p != &AddressFile->ReceiveDatagramQueue;
         p = p->Flink) {

        if (LIST_ENTRY_TO_REQUEST(p) == Request) {

            RemoveEntryList (p);
            Found = TRUE;
            break;
        }
    }

    IPX_FREE_LOCK (&Address->Lock, LockHandle);
    IoReleaseCancelSpinLock (Irp->CancelIrql);

    if (Found) {

        IPX_DEBUG(RECEIVE, ("Cancelled datagram on %lx\n", AddressFile));

        REQUEST_INFORMATION(Request) = 0;
        REQUEST_STATUS(Request) = STATUS_CANCELLED;

        IpxCompleteRequest (Request);
        ASSERT( DeviceObject->DeviceExtension == IpxDevice );
        IpxFreeRequest(IpxDevice, Request);

        IpxDereferenceAddressFile (AddressFile, AFREF_RCV_DGRAM);

    }

}   /* IpxCancelReceiveDatagram */