/*
 * COPYRIGHT:   See COPYING in the top level directory
 * PROJECT:     ReactOS TCP/IP protocol driver
 * FILE:        datalink/loopback.c
 * PURPOSE:     Loopback adapter
 * PROGRAMMERS: Casper S. Hornstrup (chorns@users.sourceforge.net)
 * REVISIONS:
 *   CSH 01/08-2000 Created
 */

#include "precomp.h"

PIP_INTERFACE Loopback = NULL;
typedef struct _LAN_WQ_ITEM {
    LIST_ENTRY ListEntry;
    PNDIS_PACKET Packet;
    PLAN_ADAPTER Adapter;
    UINT BytesTransferred;
} LAN_WQ_ITEM, *PLAN_WQ_ITEM;

/* Work around being called back into afd at Dpc level */
KSPIN_LOCK LoopWorkLock;
LIST_ENTRY LoopWorkList;
WORK_QUEUE_ITEM LoopWorkItem;
BOOLEAN LoopReceiveWorkerBusy = FALSE;

VOID STDCALL LoopReceiveWorker( PVOID Context ) {
    PLIST_ENTRY ListEntry;
    PLAN_WQ_ITEM WorkItem;
    PNDIS_PACKET Packet;
    PLAN_ADAPTER Adapter;
    UINT BytesTransferred;
    PNDIS_BUFFER NdisBuffer;
    IP_PACKET IPPacket;

    TI_DbgPrint(DEBUG_DATALINK, ("Called.\n"));

    while( (ListEntry =
	    ExInterlockedRemoveHeadList( &LoopWorkList, &LoopWorkLock )) ) {
	WorkItem = CONTAINING_RECORD(ListEntry, LAN_WQ_ITEM, ListEntry);

	TI_DbgPrint(DEBUG_DATALINK, ("WorkItem: %x\n", WorkItem));

	Packet = WorkItem->Packet;
	Adapter = WorkItem->Adapter;
	BytesTransferred = WorkItem->BytesTransferred;

	ExFreePool( WorkItem );

        IPPacket.NdisPacket = Packet;

        TI_DbgPrint(DEBUG_DATALINK, ("Packet %x Adapter %x Trans %x\n",
                                     Packet, Adapter, BytesTransferred));

        NdisGetFirstBufferFromPacket(Packet,
                                     &NdisBuffer,
                                     &IPPacket.Header,
                                     &IPPacket.ContigSize,
                                     &IPPacket.TotalSize);

	IPPacket.ContigSize = IPPacket.TotalSize = BytesTransferred;
        /* Determine which upper layer protocol that should receive
           this packet and pass it to the correct receive handler */

	TI_DbgPrint(MID_TRACE,
		    ("ContigSize: %d, TotalSize: %d, BytesTransferred: %d\n",
		     IPPacket.ContigSize, IPPacket.TotalSize,
		     BytesTransferred));

	IPPacket.Position = 0;

        IPReceive(Loopback, &IPPacket);

	FreeNdisPacket( Packet );
    }
    TI_DbgPrint(DEBUG_DATALINK, ("Leaving\n"));
    LoopReceiveWorkerBusy = FALSE;
}

VOID LoopSubmitReceiveWork(
    NDIS_HANDLE BindingContext,
    PNDIS_PACKET Packet,
    NDIS_STATUS Status,
    UINT BytesTransferred) {
    PLAN_WQ_ITEM WQItem;
    PLAN_ADAPTER Adapter = (PLAN_ADAPTER)BindingContext;
    KIRQL OldIrql;

    TcpipAcquireSpinLock( &LoopWorkLock, &OldIrql );

    WQItem = ExAllocatePool( NonPagedPool, sizeof(LAN_WQ_ITEM) );
    if( !WQItem ) {
	TcpipReleaseSpinLock( &LoopWorkLock, OldIrql );
	return;
    }

    WQItem->Packet = Packet;
    WQItem->Adapter = Adapter;
    WQItem->BytesTransferred = BytesTransferred;
    InsertTailList( &LoopWorkList, &WQItem->ListEntry );

    TI_DbgPrint(DEBUG_DATALINK, ("Packet %x Adapter %x BytesTrans %x\n",
                                 Packet, Adapter, BytesTransferred));

    if( !LoopReceiveWorkerBusy ) {
	LoopReceiveWorkerBusy = TRUE;
	ExQueueWorkItem( &LoopWorkItem, CriticalWorkQueue );
	TI_DbgPrint(DEBUG_DATALINK,
		    ("Work item inserted %x %x\n", &LoopWorkItem, WQItem));
    } else {
        TI_DbgPrint(DEBUG_DATALINK,
                    ("LOOP WORKER BUSY %x %x\n", &LoopWorkItem, WQItem));
    }
    TcpipReleaseSpinLock( &LoopWorkLock, OldIrql );
}

VOID LoopTransmit(
  PVOID Context,
  PNDIS_PACKET NdisPacket,
  UINT Offset,
  PVOID LinkAddress,
  USHORT Type)
/*
 * FUNCTION: Transmits a packet
 * ARGUMENTS:
 *   Context     = Pointer to context information (NULL)
 *   NdisPacket  = Pointer to NDIS packet to send
 *   Offset      = Offset in packet where packet data starts
 *   LinkAddress = Pointer to link address
 *   Type        = LAN protocol type (unused)
 */
{
    PCHAR PacketBuffer;
    UINT PacketLength;
    PNDIS_PACKET XmitPacket;
    NDIS_STATUS NdisStatus;

    ASSERT_KM_POINTER(NdisPacket);
    ASSERT_KM_POINTER(PC(NdisPacket));
    ASSERT_KM_POINTER(PC(NdisPacket)->DLComplete);

    TI_DbgPrint(MAX_TRACE, ("Called (NdisPacket = %x)\n", NdisPacket));

    GetDataPtr( NdisPacket, MaxLLHeaderSize, &PacketBuffer, &PacketLength );

    NdisStatus = AllocatePacketWithBuffer
        ( &XmitPacket, PacketBuffer, PacketLength );

    if( NT_SUCCESS(NdisStatus) ) {
        LoopSubmitReceiveWork
            ( NULL, XmitPacket, STATUS_SUCCESS, PacketLength );
    }

    (PC(NdisPacket)->DLComplete)
        ( PC(NdisPacket)->Context, NdisPacket, STATUS_SUCCESS );

    TI_DbgPrint(MAX_TRACE, ("Done\n"));
}

NDIS_STATUS LoopRegisterAdapter(
  PNDIS_STRING AdapterName,
  PLAN_ADAPTER *Adapter)
/*
 * FUNCTION: Registers loopback adapter with the network layer
 * ARGUMENTS:
 *   AdapterName = Unused
 *   Adapter     = Unused
 * RETURNS:
 *   Status of operation
 */
{
  NDIS_STATUS Status;
  LLIP_BIND_INFO BindInfo;

  Status = NDIS_STATUS_SUCCESS;

  TI_DbgPrint(MID_TRACE, ("Called.\n"));

  InitializeListHead( &LoopWorkList );
  ExInitializeWorkItem( &LoopWorkItem, LoopReceiveWorker, NULL );

  /* Bind the adapter to network (IP) layer */
  BindInfo.Context = NULL;
  BindInfo.HeaderSize = 0;
  BindInfo.MinFrameSize = 0;
  BindInfo.MTU = 16384;
  BindInfo.Address = NULL;
  BindInfo.AddressLength = 0;
  BindInfo.Transmit = LoopTransmit;

  Loopback = IPCreateInterface(&BindInfo);

  Loopback->Name.Buffer = L"Loopback";
  Loopback->Name.MaximumLength = Loopback->Name.Length =
      wcslen(Loopback->Name.Buffer) * sizeof(WCHAR);

  AddrInitIPv4(&Loopback->Unicast, LOOPBACK_ADDRESS_IPv4);
  AddrInitIPv4(&Loopback->Netmask, LOOPBACK_ADDRMASK_IPv4);

  IPRegisterInterface(Loopback);

  TI_DbgPrint(MAX_TRACE, ("Leaving.\n"));

  return Status;
}


NDIS_STATUS LoopUnregisterAdapter(
  PLAN_ADAPTER Adapter)
/*
 * FUNCTION: Unregisters loopback adapter with the network layer
 * ARGUMENTS:
 *   Adapter = Unused
 * RETURNS:
 *   Status of operation
 * NOTES:
 *   Does not care wether we have registered loopback adapter
 */
{
  TI_DbgPrint(MID_TRACE, ("Called.\n"));

  if (Loopback != NULL)
    {
      IPUnregisterInterface(Loopback);
      IPDestroyInterface(Loopback);
      Loopback = NULL;
    }

  TI_DbgPrint(MAX_TRACE, ("Leaving.\n"));

  return NDIS_STATUS_SUCCESS;
}
