/*

Copyright (c) 1998  Microsoft Corporation

Module Name:

	tcputil.c

Abstract:

	This module contains utility routines that used to implement the AFP/TCP interface


Author:

	Shirish Koti


Revision History:
	22 Jan 1998		Initial Version

--*/

#define	FILENUM	FILE_TCPUTIL

#include <afp.h>
#include <scavengr.h>



/***	DsiInit
 *
 *	This routine initialization of DSI related globals
 *
 *  Returns:  nothing
 *
 */
VOID
DsiInit(
    IN VOID
)
{
    DsiTcpAdapter = NULL;

    INITIALIZE_SPIN_LOCK(&DsiAddressLock);

    INITIALIZE_SPIN_LOCK(&DsiResourceLock);

    InitializeListHead(&DsiFreeRequestList);
    InitializeListHead(&DsiIpAddrList);

    KeInitializeEvent(&DsiShutdownEvent, NotificationEvent, False);

    //
    // initialize the function table of entry points into DSI
    //
    AfpDsiEntries.asp_AtalkAddr.Address = 0;
    AfpDsiEntries.asp_AspCtxt   = NULL;
    AfpDsiEntries.asp_SetStatus = DsiAfpSetStatus;
    AfpDsiEntries.asp_CloseConn = DsiAfpCloseConn;
    AfpDsiEntries.asp_FreeConn  = DsiAfpFreeConn;
    AfpDsiEntries.asp_ListenControl = DsiAfpListenControl;
    AfpDsiEntries.asp_WriteContinue = DsiAfpWriteContinue;
    AfpDsiEntries.asp_Reply = DsiAfpReply;
    AfpDsiEntries.asp_SendAttention = DsiAfpSendAttention;

}



/***	DsiCreateAdapter
 *
 *	This routine creates an adapter object.  It's called whenever TDI tells us that
 *  a tcpip interface became available
 *
 *  Returns:  status of operation
 *
 */
NTSTATUS FASTCALL
DsiCreateAdapter(
    IN VOID
)
{
    NTSTATUS            status;
    PTCPADPTR           pTcpAdptr;
    PTCPADPTR           pCurrTcpAdptr;
    KIRQL               OldIrql;
    HANDLE              FileHandle;
    PFILE_OBJECT        pFileObject;
    DWORD               i;


    KeClearEvent(&DsiShutdownEvent);

    pTcpAdptr = (PTCPADPTR)AfpAllocZeroedNonPagedMemory(sizeof(TCPADPTR));
    if (pTcpAdptr == NULL)
    {
        DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_ERR,
            ("DsiCreateInterface: alloc for PTCPADPTR failed\n"));

        status = STATUS_INSUFFICIENT_RESOURCES;
        goto DsiCreateAdapter_ErrExit;
    }

    pTcpAdptr->adp_Signature = DSI_ADAPTER_SIGNATURE;

    pTcpAdptr->adp_RefCount  = 1;                   // creation refcount

    pTcpAdptr->adp_State     = TCPADPTR_STATE_INIT;

    InitializeListHead(&pTcpAdptr->adp_ActiveConnHead);
    InitializeListHead(&pTcpAdptr->adp_FreeConnHead);

    INITIALIZE_SPIN_LOCK(&pTcpAdptr->adp_SpinLock);

    pTcpAdptr->adp_FileHandle = INVALID_HANDLE_VALUE;
    pTcpAdptr->adp_pFileObject = NULL;


    //
    // ok, save this adapter as our global adapter (there can only be one
    // "adapter" active at any time.
    //

    ACQUIRE_SPIN_LOCK(&DsiAddressLock, &OldIrql);

    ASSERT(DsiTcpAdapter == NULL);

    if (DsiTcpAdapter == NULL)
    {
        DsiTcpAdapter = pTcpAdptr;
        status = STATUS_SUCCESS;
        RELEASE_SPIN_LOCK(&DsiAddressLock, OldIrql);

    }
    else
    {
        DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_ERR,
            ("DsiCreateAdapter: DsiTcpAdapter is not NULL!\n"));
        ASSERT(0);

        status = STATUS_ADDRESS_ALREADY_EXISTS;
        RELEASE_SPIN_LOCK(&DsiAddressLock, OldIrql);
        goto DsiCreateAdapter_ErrExit;
    }


    //
    // create TDI address for the AFP port
    //
    status = DsiOpenTdiAddress(pTcpAdptr,
                               &FileHandle,
                               &pFileObject);
    if (!NT_SUCCESS(status))
    {
	    DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_ERR,
            ("DsiCreateAdapter: ...TdiAddr.. failed %lx on %lx!\n",
            status,pTcpAdptr));

        goto DsiCreateAdapter_ErrExit;
    }


    ACQUIRE_SPIN_LOCK(&pTcpAdptr->adp_SpinLock, &OldIrql);

    pTcpAdptr->adp_FileHandle = FileHandle;
    pTcpAdptr->adp_pFileObject = pFileObject;

    // mark that we now have opened the tdi address object
    pTcpAdptr->adp_State |= TCPADPTR_STATE_BOUND;

    // we are going to create DSI_INIT_FREECONNLIST_SIZE connections to put
    // on the free list.  Idea is at any time, so many (currently 2) connections
    // should be on the free list.

    pTcpAdptr->adp_RefCount += DSI_INIT_FREECONNLIST_SIZE;

    RELEASE_SPIN_LOCK(&pTcpAdptr->adp_SpinLock, OldIrql);

    //
    // now, schedule an event to create those connections for the free list
    //
    for (i=0; i<DSI_INIT_FREECONNLIST_SIZE; i++)
    {
        DsiScheduleWorkerEvent(DsiCreateTcpConn, pTcpAdptr);
    }

    ACQUIRE_SPIN_LOCK(&DsiAddressLock, &OldIrql);
    AfpServerBoundToTcp = TRUE;
    RELEASE_SPIN_LOCK(&DsiAddressLock, OldIrql);

    // start off tickle timer (monitor all connections to see who needs a tickle)
    AfpScavengerScheduleEvent(DsiSendTickles, NULL, DSI_TICKLE_TIMER, False);

    DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_ERR,("AFP/TCP bound and ready\n"));

    //
    // if we came this far, all went well: return success
    //
    return(STATUS_SUCCESS);


//
// Error path
//
DsiCreateAdapter_ErrExit:

    DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_ERR,
        ("DsiCreateAdapter: couldn't create global adapter (%lx)\n",status));

    ASSERT(0);

    if (status != STATUS_ADDRESS_ALREADY_EXISTS)
    {
        ACQUIRE_SPIN_LOCK(&DsiAddressLock, &OldIrql);
        DsiTcpAdapter = NULL;
        RELEASE_SPIN_LOCK(&DsiAddressLock, OldIrql);
    }

    if (pTcpAdptr)
    {
        AfpFreeMemory(pTcpAdptr);
    }

    return(status);
}



/***	DsiCreateTcpConn
 *
 *	This routine creates a connection object, creates a tdi connection for it
 *  and associates it with the tdi address object for the AFP port, and finally
 *  puts it on the free connections list of the adapter in question.
 *
 *  Parm IN:  pTcpAdptr - adapter context
 *
 *  Returns:  status of operation
 *
 */
NTSTATUS FASTCALL
DsiCreateTcpConn(
    IN PTCPADPTR    pTcpAdptr
)
{
    PTCPCONN    pTcpConn;
    NTSTATUS    status;
    KIRQL       OldIrql;


    ASSERT(pTcpAdptr->adp_Signature == DSI_ADAPTER_SIGNATURE);


    pTcpConn = (PTCPCONN)AfpAllocZeroedNonPagedMemory(sizeof(TCPCONN));
    if (pTcpConn == NULL)
    {
        DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_ERR,
            ("DsiCreateTcpConn: alloc for TCPCONN failed\n"));

        // remove the CONN refcount (we put before calling this routine)
        DsiDereferenceAdapter(pTcpAdptr);
        return(STATUS_INSUFFICIENT_RESOURCES);
    }

    pTcpConn->con_pTcpAdptr      = pTcpAdptr;
    pTcpConn->con_Signature      = DSI_CONN_SIGNATURE;
    pTcpConn->con_State          = TCPCONN_STATE_INIT;
    pTcpConn->con_RcvState       = DSI_NEW_REQUEST;
    pTcpConn->con_DestIpAddr     = 0;
    pTcpConn->con_RefCount       = 1;
    pTcpConn->con_pDsiReq        = NULL;
    pTcpConn->con_FileHandle     = INVALID_HANDLE_VALUE;
    pTcpConn->con_pFileObject    = NULL;

    DBGREFCOUNT(("DsiCreateTcpConn: CREATION inc %lx (%d  %d,%d)\n",
        pTcpConn,pTcpConn->con_RefCount,pTcpConn->con_State,pTcpConn->con_RcvState));

    InitializeListHead(&pTcpConn->con_PendingReqs);

    //
    // initialize the TDI stuff for this connection and open handles
    //
    status = DsiOpenTdiConnection(pTcpConn);
    if (!NT_SUCCESS(status))
    {
        DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_ERR,
            ("DsiCreateTcpConn: ..TdiConn.. failed %lx\n",status));

        // remove the CONN refcount (we put before calling this routine)
        DsiDereferenceAdapter(pTcpAdptr);
        AfpFreeMemory(pTcpConn);
        return(status);
    }


    //
    // associate this connection with the addr object
    //
    status = DsiAssociateTdiConnection(pTcpConn);
    if (!NT_SUCCESS(status))
    {
        DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_ERR,
            ("DsiCreateTcpConn: ..AssociateTdiConn.. failed %lx\n",status));

        DsiCloseTdiConnection(pTcpConn);
        AfpFreeMemory(pTcpConn);
        // remove the CONN refcount (we put before calling this routine)
        DsiDereferenceAdapter(pTcpAdptr);
        return(status);
    }

    //
    // the connection is ready to be queued to the Free list.  Make sure the
    // addr object isn't closing before putting this puppy on the list
    //
    ACQUIRE_SPIN_LOCK(&pTcpAdptr->adp_SpinLock, &OldIrql);

    if (!(pTcpAdptr->adp_State & TCPADPTR_STATE_CLOSING))
    {
        InsertTailList(&pTcpAdptr->adp_FreeConnHead,&pTcpConn->con_Linkage);
        pTcpAdptr->adp_NumFreeConnections++;
        status = STATUS_SUCCESS;
    }
    else
    {
        DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_ERR,
            ("DsiCreateTcpConn: Failed #2, pTcpAdptr %lx is closing\n",pTcpAdptr));

        status = STATUS_INVALID_ADDRESS;
    }

    RELEASE_SPIN_LOCK(&pTcpAdptr->adp_SpinLock, OldIrql);

    //
    // if something went wrong, undo everything
    //
    if (!NT_SUCCESS(status))
    {
        DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_ERR,
            ("DsiCreateTcpConn: something went wrong status=%lx, conn not created\n",status));

        // close the TDI handles
        DsiCloseTdiConnection(pTcpConn);

        AfpFreeMemory(pTcpConn);

        // remove the CONN refcount (we put before calling this routine)
        DsiDereferenceAdapter(pTcpAdptr);
    }
    else
    {
        DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_INFO,
            ("DsiCreateTcpConn: put new connection %lx on free list\n",pTcpConn));
    }

    return(status);
}



/***	DsiAddIpaddressToList
 *
 *	This routine saves an "active" ipaddress in our list of ipaddresses
 *
 *  Parm IN:  IpAddress - the ipaddress to save
 *
 *  Returns:  result of the operation
 *
 */
NTSTATUS
DsiAddIpaddressToList(
    IN  IPADDRESS   IpAddress
)
{
    KIRQL           OldIrql;
    PLIST_ENTRY     pList;
    PIPADDRENTITY   pIpaddrEntity;
    PIPADDRENTITY   pTmpIpaddrEntity;
    BOOLEAN         fAlreadyPresent=FALSE;


    pIpaddrEntity =
        (PIPADDRENTITY)AfpAllocZeroedNonPagedMemory(sizeof(IPADDRENTITY));
    if (pIpaddrEntity == NULL)
    {
	    DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_ERR,
            ("DsiAddIpaddressToList: malloc failed! (%lx)\n",IpAddress));
        return(STATUS_INSUFFICIENT_RESOURCES);
    }

    pIpaddrEntity->IpAddress = IpAddress;

    ACQUIRE_SPIN_LOCK(&DsiAddressLock, &OldIrql);

    pList = DsiIpAddrList.Flink;

    while (pList != &DsiIpAddrList)
    {
        pTmpIpaddrEntity = CONTAINING_RECORD(pList, IPADDRENTITY, Linkage);
        if (pTmpIpaddrEntity->IpAddress == IpAddress)
        {
            fAlreadyPresent = TRUE;
            break;
        }
        pList = pList->Flink;
    }

    if (fAlreadyPresent)
    {
	    DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_ERR,
            ("DsiAddIpaddressToList: %d.%d.%d.%d already present!\n",
            (IpAddress>>24)&0xFF,(IpAddress>>16)&0xFF,(IpAddress>>8)&0xFF,IpAddress&0xFF));

        ASSERT(0);

        RELEASE_SPIN_LOCK(&DsiAddressLock, OldIrql);
        return(STATUS_ADDRESS_ALREADY_EXISTS);
    }

    InsertTailList(&DsiIpAddrList, &pIpaddrEntity->Linkage);

    RELEASE_SPIN_LOCK(&DsiAddressLock, OldIrql);

    return(STATUS_SUCCESS);

}


/***	DsiRemoveIpaddressFromList
 *
 *	This routine remove ipaddress from our list of ipaddresses
 *
 *  Parm IN:  IpAddress - the ipaddress to remove
 *
 *  Returns:  TRUE if we removed the ipaddress, FALSE if we didn't
 *
 */
BOOLEAN
DsiRemoveIpaddressFromList(
    IN  IPADDRESS   IpAddress
)
{
    KIRQL           OldIrql;
    PLIST_ENTRY     pList;
    PIPADDRENTITY   pIpaddrEntity;
    BOOLEAN         fFoundInList=FALSE;


    ACQUIRE_SPIN_LOCK(&DsiAddressLock, &OldIrql);

    pList = DsiIpAddrList.Flink;

    while (pList != &DsiIpAddrList)
    {
        pIpaddrEntity = CONTAINING_RECORD(pList, IPADDRENTITY, Linkage);
        if (pIpaddrEntity->IpAddress == IpAddress)
        {
            fFoundInList = TRUE;
            break;
        }
        pList = pList->Flink;
    }

    if (!fFoundInList)
    {
	    DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_ERR,
            ("DsiRemoveIpaddressFromList: %d.%d.%d.%d not in the list!\n",
            (IpAddress>>24)&0xFF,(IpAddress>>16)&0xFF,(IpAddress>>8)&0xFF,IpAddress&0xFF));

        ASSERT(0);

        RELEASE_SPIN_LOCK(&DsiAddressLock, OldIrql);
        return(FALSE);
    }

    RemoveEntryList(&pIpaddrEntity->Linkage);

    RELEASE_SPIN_LOCK(&DsiAddressLock, OldIrql);

    AfpFreeMemory(pIpaddrEntity);

    return(TRUE);
}



/***	DsiGetRequest
 *
 *	This routine allocates a DSI Request structure and returns.  For performance
 *  reasons, we don't alloc new memory each time, but save a list of these
 *
 *  Parm IN:  nothin'
 *
 *  Returns:  pointer to a DSIREQ strucutre (NULL on failure)
 *
 */
PDSIREQ
DsiGetRequest(
    IN VOID
)
{
    PDSIREQ         pDsiReq=NULL;
    PLIST_ENTRY     pList;
    KIRQL           OldIrql;


    ACQUIRE_SPIN_LOCK(&DsiResourceLock, &OldIrql);

    if (!IsListEmpty(&DsiFreeRequestList))
    {
        pList = RemoveHeadList(&DsiFreeRequestList);
        pDsiReq = CONTAINING_RECORD(pList, DSIREQ, dsi_Linkage);

        ASSERT(DsiFreeRequestListSize > 0);
        DsiFreeRequestListSize--;

        RtlZeroMemory(pDsiReq, sizeof(DSIREQ));
    }

    RELEASE_SPIN_LOCK(&DsiResourceLock, OldIrql);

    if (pDsiReq == NULL)
    {
        pDsiReq = (PDSIREQ)AfpAllocZeroedNonPagedMemory(sizeof(DSIREQ));
    }

    if (pDsiReq != NULL)
    {
        pDsiReq->dsi_Signature = DSI_REQUEST_SIGNATURE;

        InitializeListHead(&pDsiReq->dsi_Linkage);
    }
    else
    {
        DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_ERR,
            ("DsiGetRequest: malloc failed!\n"));
    }

    return(pDsiReq);
}



/***	DsiGetReqBuffer
 *
 *	This routine allocates a buffer to hold either a header or a command
 *  The likelihood of this function getting called is pretty slim (basically
 *  if a packet is fragmented by TCP).  So we simply make a call to alloc
 *
 *  Parm IN:  BufLen - length of the buffer requested
 *
 *  Returns:  pointer to a buffer (NULL on failure)
 *
 */
PBYTE
DsiGetReqBuffer(
    IN DWORD    BufLen
)
{
    PBYTE       pBuffer=NULL;

    pBuffer = AfpAllocNonPagedMemory(BufLen);

#if DBG
    if (pBuffer == NULL)
    {
        DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_ERR,
        ("DsiGetReqBuffer: malloc failed!\n"));
    }
#endif

    return(pBuffer);
}


/***	DsiFreeRequest
 *
 *	This routine frees up a previously allocated DSI Request structure.  Again,
 *  for performance reasons, we don't free up the memory but put this in a list
 *
 *  Parm IN:  pDsiReq - the request to be freed
 *
 *  Returns:  nothing
 *
 */
VOID
DsiFreeRequest(
    PDSIREQ     pDsiReq
)
{

    KIRQL           OldIrql;
    BOOLEAN         fQueueTooBig = TRUE;


    if ((pDsiReq->dsi_PartialBuf != NULL) &&
        (pDsiReq->dsi_PartialBuf != &pDsiReq->dsi_RespHeader[0]))
    {
        ASSERT(pDsiReq->dsi_PartialBufSize > 0);
        DsiFreeReqBuffer(pDsiReq->dsi_PartialBuf);

        pDsiReq->dsi_PartialBuf = NULL;
        pDsiReq->dsi_PartialBufSize = 0;
    }

    // if there was an Mdl we got via cache mgr, it had better be returned to system
    ASSERT(pDsiReq->dsi_AfpRequest.rq_CacheMgrContext == NULL);

    //
    // if we came here via abnormal disconnect, this could be non-null
    //
    if (pDsiReq->dsi_pDsiAllocedMdl)
    {
        AfpFreeMdl(pDsiReq->dsi_pDsiAllocedMdl);
    }

#if DBG
    RtlFillMemory(pDsiReq, sizeof(DSIREQ), 'f');
#endif

    ACQUIRE_SPIN_LOCK(&DsiResourceLock, &OldIrql);

    if (DsiFreeRequestListSize < DsiNumTcpConnections)
    {
        InsertTailList(&DsiFreeRequestList, &pDsiReq->dsi_Linkage);
        DsiFreeRequestListSize++;
        fQueueTooBig = FALSE;
    }

    RELEASE_SPIN_LOCK(&DsiResourceLock, OldIrql);

    if (fQueueTooBig)
    {
        AfpFreeMemory(pDsiReq);
    }


    return;
}


/***	DsiFreeReqBuffer
 *
 *	This routine allocates a buffer to hold either a header or a command
 *  The likelihood of this function getting called is pretty slim (basically
 *  if a packet is fragmented by TCP).  So we simply make a call to alloc
 *
 *  Parm IN:  BufLen - length of the buffer requested
 *
 *  Returns:  pointer to a buffer (NULL on failure)
 *
 */
VOID
DsiFreeReqBuffer(
    IN PBYTE    pBuffer
)
{
    ASSERT(pBuffer != NULL);

    AfpFreeMemory(pBuffer);

    return;
}


/***	DsiDereferenceAdapter
 *
 *	This routine dereferences the adapter object.  When refcount goes to 0, it
 *  removes it from the global list of adapters.  If at task time, it calls a
 *  routine to close tcp handles and free the memory.  If at dpc, it schedules
 *  an event to do the same.
 *
 *  Parm IN:  pTcpAdptr - adapter context
 *
 *  Returns:  nothin'
 *
 */
VOID
DsiDereferenceAdapter(
    IN PTCPADPTR    pTcpAdptr
)
{
    KIRQL       OldIrql;
    BOOLEAN     fDone=FALSE;


    ASSERT(pTcpAdptr->adp_Signature == DSI_ADAPTER_SIGNATURE);

    ACQUIRE_SPIN_LOCK(&pTcpAdptr->adp_SpinLock, &OldIrql);

    pTcpAdptr->adp_RefCount--;

    if (pTcpAdptr->adp_RefCount == 0)
    {
        fDone = TRUE;
        ASSERT(pTcpAdptr->adp_State & TCPADPTR_STATE_CLOSING);
    }

    RELEASE_SPIN_LOCK(&pTcpAdptr->adp_SpinLock, OldIrql);

    if (!fDone)
    {
        return;
    }

    //
    // this dude's life is over: do the needful to bid goodbye
    //

    // if we are at DPC, we need to do all the cleanup (file handle closing etc.)
    // at task time: queue an event
    if (KeGetCurrentIrql() == DISPATCH_LEVEL)
    {
        // queue an event, since we are at dpc
        DsiScheduleWorkerEvent(DsiFreeAdapter, pTcpAdptr);
    }
    else
    {
        DsiFreeAdapter(pTcpAdptr);
    }

    return;
}



/***	DsiDereferenceConnection
 *
 *	This routine dereferences the connection object.  When refcount goes to 0, it
 *  removes it from the list of connections.  If at task time, it calls a
 *  routine to close tcp handles and free the memory.  If at dpc, it schedules
 *  an event to do the same.
 *
 *  Parm IN:  pTcpConn - connection context
 *
 *  Returns:  nothin'
 *
 */
VOID
DsiDereferenceConnection(
    IN PTCPCONN     pTcpConn
)
{
    KIRQL       OldIrql;
    BOOLEAN     fDone=FALSE;


    ASSERT(VALID_TCPCONN(pTcpConn));

    ACQUIRE_SPIN_LOCK(&pTcpConn->con_SpinLock, &OldIrql);

    pTcpConn->con_RefCount--;

    if (pTcpConn->con_RefCount == 0)
    {
        fDone = TRUE;
        ASSERT(pTcpConn->con_State & TCPCONN_STATE_CLOSING);
    }

    RELEASE_SPIN_LOCK(&pTcpConn->con_SpinLock, OldIrql);

    if (!fDone)
    {
        return;
    }

    //
    // this dude's life is over: do the needful to bid goodbye
    //

#if 0
    // if we are at DPC, we need to do all the cleanup (file handle closing etc.)
    // at task time: queue an event
    if (KeGetCurrentIrql() == DISPATCH_LEVEL)
    {
        // queue an event, since we are at dpc
        DsiScheduleWorkerEvent(DsiFreeConnection, pTcpConn);
    }
    else
    {
        DsiFreeConnection(pTcpConn);
    }
#endif

    // schedule a worker event to free this connection
    DsiScheduleWorkerEvent(DsiFreeConnection, pTcpConn);

    return;
}


/***	DsiDestroyAdapter
 *
 *	This routine destroys the global adapter object.
 *
 *  Returns:  status of operation
 *
 */
NTSTATUS
DsiDestroyAdapter(
    IN VOID
)
{
    KIRQL               OldIrql;
    PLIST_ENTRY         pFreeList;
    PLIST_ENTRY         pActiveList;
    PTCPCONN            pTcpConn;
    BOOLEAN             fAlreadyCleanedUp=FALSE;


    if (DsiTcpAdapter == NULL)
    {
        DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_ERR,
            ("DsiDestroyAdapter: adapter gone!  How did this happen!!\n"));

        // unblock the event!
        KeSetEvent(&DsiShutdownEvent, IO_NETWORK_INCREMENT, False);
        return(STATUS_SUCCESS);
    }

    // stop the tickle timer
    if (!AfpScavengerKillEvent(DsiSendTickles, NULL))
    {
        DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_ERR,
            ("DsiDestroyAdapter: TickleTimer not running or hit timing window!!\n"));
    }

    ACQUIRE_SPIN_LOCK(&DsiTcpAdapter->adp_SpinLock, &OldIrql);

    if (DsiTcpAdapter->adp_State & TCPADPTR_STATE_CLEANED_UP)
    {
        fAlreadyCleanedUp = TRUE;
    }

    DsiTcpAdapter->adp_State |= TCPADPTR_STATE_CLOSING;
    DsiTcpAdapter->adp_State |= TCPADPTR_STATE_CLEANED_UP;

    if (fAlreadyCleanedUp)
    {
        DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_ERR,
            ("DsiDestroyAdapter: already destroyed!\n"));

        ASSERT(IsListEmpty(&DsiTcpAdapter->adp_FreeConnHead));
        ASSERT(IsListEmpty(&DsiTcpAdapter->adp_ActiveConnHead));
        ASSERT(DsiTcpAdapter->adp_NumFreeConnections == 0);

        RELEASE_SPIN_LOCK(&DsiTcpAdapter->adp_SpinLock, OldIrql);
        return(STATUS_SUCCESS);
    }

    //
    // free up all the connections from the Free list
    //
    while (!IsListEmpty(&DsiTcpAdapter->adp_FreeConnHead))
    {
        pFreeList = DsiTcpAdapter->adp_FreeConnHead.Flink;

        pTcpConn = CONTAINING_RECORD(pFreeList, TCPCONN, con_Linkage);

        RemoveEntryList(&pTcpConn->con_Linkage);

        ASSERT(DsiTcpAdapter->adp_NumFreeConnections > 0);

        DsiTcpAdapter->adp_NumFreeConnections--;

        InitializeListHead(&pTcpConn->con_Linkage);

        RELEASE_SPIN_LOCK(&DsiTcpAdapter->adp_SpinLock, OldIrql);

        pTcpConn->con_State |= TCPCONN_STATE_CLOSING;
        DsiDereferenceConnection(pTcpConn);

        DBGREFCOUNT(("DsiDestroyAdapter: Creation dec %lx (%d  %d,%d)\n",
            pTcpConn,pTcpConn->con_RefCount,pTcpConn->con_State,pTcpConn->con_RcvState));

        ACQUIRE_SPIN_LOCK(&DsiTcpAdapter->adp_SpinLock, &OldIrql);
    }

    //
    // kill all the connections from the Active list
    //
    pActiveList = DsiTcpAdapter->adp_ActiveConnHead.Flink;

    while (pActiveList != &DsiTcpAdapter->adp_ActiveConnHead)
    {
        pTcpConn = CONTAINING_RECORD(pActiveList, TCPCONN, con_Linkage);

        pActiveList = pActiveList->Flink;

        ACQUIRE_SPIN_LOCK_AT_DPC(&pTcpConn->con_SpinLock);

        // if this connection is already closing, skip it
        if (pTcpConn->con_State & TCPCONN_STATE_CLOSING)
        {
            RELEASE_SPIN_LOCK_FROM_DPC(&pTcpConn->con_SpinLock);
            continue;
        }

        // put ABORT refcount for now
        pTcpConn->con_RefCount++;

        DBGREFCOUNT(("DsiDestroyAdapter: ABORT inc %lx (%d  %d,%d)\n",
            pTcpConn,pTcpConn->con_RefCount,pTcpConn->con_State,pTcpConn->con_RcvState));

        RemoveEntryList(&pTcpConn->con_Linkage);
        InitializeListHead(&pTcpConn->con_Linkage);

        RELEASE_SPIN_LOCK_FROM_DPC(&pTcpConn->con_SpinLock);

        RELEASE_SPIN_LOCK(&DsiTcpAdapter->adp_SpinLock, OldIrql);

        DsiAbortConnection(pTcpConn);

        // remove that ABORT refcount
        DsiDereferenceConnection(pTcpConn);

        DBGREFCOUNT(("DsiDestroyAdapter: ABORT dec %lx (%d  %d,%d)\n",
            pTcpConn,pTcpConn->con_RefCount,pTcpConn->con_State,pTcpConn->con_RcvState));

        ACQUIRE_SPIN_LOCK(&DsiTcpAdapter->adp_SpinLock, &OldIrql);

        // since we released the lock, things could have changed: start over
        pActiveList = DsiTcpAdapter->adp_ActiveConnHead.Flink;
    }

    RELEASE_SPIN_LOCK(&DsiTcpAdapter->adp_SpinLock, OldIrql);

    // remove the creation refcount
    DsiDereferenceAdapter(DsiTcpAdapter);

    return(STATUS_SUCCESS);

}


/***	DsiKillConnection
 *
 *	This routine kills an active connection.
 *
 *  Parm IN:  pTcpConn - the connection to kill
 *
 *  Returns:  TRUE if we killed it, FALSE if we couldn't
 *
 */
BOOLEAN
DsiKillConnection(
    IN PTCPCONN     pTcpConn,
    IN DWORD        DiscFlag
)
{
    KIRQL           OldIrql;
    NTSTATUS        status;
    PDSIREQ         pPartialDsiReq=NULL;
    BOOLEAN         fFirstVisit=TRUE;



    ACQUIRE_SPIN_LOCK(&pTcpConn->con_SpinLock, &OldIrql);

    if (pTcpConn->con_State & TCPCONN_STATE_CLEANED_UP)
    {
        DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_WARN,
            ("DsiKillConnection: connection %lx already cleaned up\n",pTcpConn));

        fFirstVisit = FALSE;
    }

    pTcpConn->con_State &= ~TCPCONN_STATE_CONNECTED;
    pTcpConn->con_State |= (TCPCONN_STATE_CLOSING | TCPCONN_STATE_CLEANED_UP);

    //
    // if a request is waiting for an mdl to become available, don't touch it here.
    // When afp returns with mdl (or null mdl), we'll clean up this request
    //
    if (pTcpConn->con_RcvState != DSI_AWAITING_WRITE_MDL)
    {
        pPartialDsiReq = pTcpConn->con_pDsiReq;
        pTcpConn->con_pDsiReq = NULL;
    }

    RELEASE_SPIN_LOCK(&pTcpConn->con_SpinLock, OldIrql);

    if (pPartialDsiReq)
    {
        // if we had allocated an mdl, let afp know so afp can free it
        if ((pPartialDsiReq->dsi_Command == DSI_COMMAND_WRITE) &&
            (pPartialDsiReq->dsi_AfpRequest.rq_WriteMdl != NULL))
        {
            AfpCB_RequestNotify(STATUS_REMOTE_DISCONNECT,
                                pTcpConn->con_pSda,
                                &pPartialDsiReq->dsi_AfpRequest);
        }

        DsiFreeRequest(pPartialDsiReq);

        // remove the REQUEST refcount
        DsiDereferenceConnection(pTcpConn);

        DBGREFCOUNT(("DsiKillConnection: REQUEST dec %lx (%d  %d,%d)\n",
            pTcpConn,pTcpConn->con_RefCount,pTcpConn->con_State,pTcpConn->con_RcvState));
    }

    status = (DiscFlag == TDI_DISCONNECT_ABORT)?
                STATUS_LOCAL_DISCONNECT: STATUS_REMOTE_DISCONNECT;

    // give AFP the bad news
    DsiDisconnectWithAfp(pTcpConn, status);

    // give TCP the bad news
    DsiDisconnectWithTcp(pTcpConn, DiscFlag);

    // remove the Creation refcount if this is the first time we're visiting
    // this routine
    if (fFirstVisit)
    {
        DsiDereferenceConnection(pTcpConn);

        DBGREFCOUNT(("DsiKillConnection: Creation dec %lx (%d  %d,%d)\n",
            pTcpConn,pTcpConn->con_RefCount,pTcpConn->con_State,pTcpConn->con_RcvState));
    }

    return(TRUE);
}



/***	DsiFreeAdapter
 *
 *	This routine frees the adapter object after closing the tcp handles
 *
 *  Parm IN:  pTcpAdptr - adapter object
 *
 *  Returns:  result of the operation
 *
 */
NTSTATUS FASTCALL
DsiFreeAdapter(
    IN PTCPADPTR    pTcpAdptr
)
{

    BOOLEAN         fRecreateAdapter=FALSE;
    KIRQL           OldIrql;


    ASSERT(KeGetCurrentIrql() != DISPATCH_LEVEL);

    ASSERT(pTcpAdptr->adp_Signature == DSI_ADAPTER_SIGNATURE);
    ASSERT(pTcpAdptr->adp_State & TCPADPTR_STATE_CLOSING);
    ASSERT(pTcpAdptr->adp_RefCount == 0);

    // close file handles
    DsiCloseTdiAddress(pTcpAdptr);

    ACQUIRE_SPIN_LOCK(&DsiAddressLock, &OldIrql);

    DsiTcpAdapter = NULL;
    AfpServerBoundToTcp = FALSE;

    //
    // it's possible that by the time we did all the cleanup and everything,
    // an ipaddress(es) became available.  If that has happened, go ahead and
    // create the global adapter again!
    //
    if (!IsListEmpty(&DsiIpAddrList))
    {
        fRecreateAdapter = TRUE;
    }

    // if we are shutting down, don't create the adapter again!
    if ((AfpServerState == AFP_STATE_STOP_PENDING) ||
        (AfpServerState == AFP_STATE_SHUTTINGDOWN) ||
        (AfpServerState == AFP_STATE_STOPPED))
    {
        fRecreateAdapter = FALSE;
    }

    RELEASE_SPIN_LOCK(&DsiAddressLock, OldIrql);

    ASSERT(pTcpAdptr->adp_pFileObject == NULL);
    ASSERT(pTcpAdptr->adp_FileHandle == INVALID_HANDLE_VALUE);

    DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_ERR,
        ("DsiFreeAdapter: freeing adapter %lx\n",pTcpAdptr));

    AfpFreeMemory(pTcpAdptr);

    // wake up that blocked thread!
    KeSetEvent(&DsiShutdownEvent, IO_NETWORK_INCREMENT, False);

    if (fRecreateAdapter)
    {
        DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_ERR,
            ("DsiFreeAdapter: ipaddress came in, so recreating global adapter\n"));

        DsiCreateAdapter();
    }

    return(STATUS_SUCCESS);
}



/***	DsiFreeConnection
 *
 *	This routine frees the connection object after closing the tcp handles
 *
 *  Parm IN:  pTcpConn - connection object
 *
 *  Returns:  result of the operation
 *
 */
NTSTATUS FASTCALL
DsiFreeConnection(
    IN PTCPCONN     pTcpConn
)
{

    KIRQL       OldIrql;
    PTCPADPTR   pTcpAdptr;
    IPADDRESS   IpAddress;


    ASSERT(KeGetCurrentIrql() != DISPATCH_LEVEL);

    ASSERT(pTcpConn->con_Signature == DSI_CONN_SIGNATURE);
    ASSERT(pTcpConn->con_State & TCPCONN_STATE_CLOSING);
    ASSERT(pTcpConn->con_RefCount == 0);

    pTcpAdptr = pTcpConn->con_pTcpAdptr;

    ASSERT(pTcpAdptr->adp_Signature == DSI_ADAPTER_SIGNATURE);

    // close file handles
    DsiCloseTdiConnection(pTcpConn);

    // remove this puppy from the list
    ACQUIRE_SPIN_LOCK(&pTcpAdptr->adp_SpinLock, &OldIrql);
    RemoveEntryList(&pTcpConn->con_Linkage);
    RELEASE_SPIN_LOCK(&pTcpAdptr->adp_SpinLock, OldIrql);

    // remove the CONN refcount for this connection
    DsiDereferenceAdapter(pTcpConn->con_pTcpAdptr);

    ASSERT(pTcpConn->con_pFileObject == NULL);
    ASSERT(pTcpConn->con_FileHandle == INVALID_HANDLE_VALUE);

#if DBG
    IpAddress = pTcpConn->con_DestIpAddr;

    if (IpAddress)
    {
        DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_WARN,
            ("DsiFreeConnection: freeing connection on %d.%d.%d.%d (%lx)\n",
            (IpAddress>>24)&0xFF,(IpAddress>>16)&0xFF,(IpAddress>>8)&0xFF,
            IpAddress&0xFF,pTcpConn));
    }

    pTcpConn->con_Signature = 0xDEADBEEF;
#endif

    AfpFreeMemory(pTcpConn);

    return(STATUS_SUCCESS);
}


/***	DsiGetIpAddrBlob
 *
 *	This routine generates a 'blob' that gets plugged into the ServerInfo buffer.
 *  Here we walk the ipaddr list and generate a blob with all the available
 *  ipaddresses (6-byte-per-ipaddress_
 *
 *  Parm OUT: pIpAddrCount - how many ipaddresses there are in the system
 *            ppIpAddrBlob - pointer to a pointer to a buffer
 *
 *  Returns:  status of operation
 *
 */
NTSTATUS
DsiGetIpAddrBlob(
    OUT DWORD    *pIpAddrCount,
    OUT PBYTE    *ppIpAddrBlob
)
{
    KIRQL               OldIrql;
    PLIST_ENTRY         pList;
    DWORD               AddrCount=0;
    DWORD               TmpCount=0;
    PBYTE               AddrBlob;
    PBYTE               pCurrentBlob;
    PIPADDRENTITY       pIpaddrEntity;


    *pIpAddrCount = 0;
    *ppIpAddrBlob = NULL;

    ACQUIRE_SPIN_LOCK(&DsiAddressLock, &OldIrql);

    if (!DsiTcpEnabled)
    {
        RELEASE_SPIN_LOCK(&DsiAddressLock, OldIrql);

        DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_ERR,
            ("DsiGetIpAddrBlob: Server is disabled\n"));

        return(STATUS_SUCCESS);
    }


    //
    // find out how many ipaddresses are there on the list
    //
    AddrCount = 0;
    pList = DsiIpAddrList.Flink;
    while (pList != &DsiIpAddrList)
    {
        AddrCount++;
        pList = pList->Flink;
    }

    if (AddrCount == 0)
    {
        RELEASE_SPIN_LOCK(&DsiAddressLock, OldIrql);

        DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_ERR,
            ("DsiGetIpAddrBlob: calling AfpSetServerStatus with 0 addrs\n"));

        return(STATUS_SUCCESS);
    }

    if (AddrCount > DSI_MAX_IPADDR_COUNT)
    {
        AddrCount = DSI_MAX_IPADDR_COUNT;
    }

    AddrBlob = AfpAllocZeroedNonPagedMemory(AddrCount * DSI_NETWORK_ADDR_LEN);

    if (AddrBlob == NULL)
    {
        RELEASE_SPIN_LOCK(&DsiAddressLock, OldIrql);

	    DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_ERR,
            ("DsiGetIpAddrBlob: malloc failed\n"));

        return(STATUS_INSUFFICIENT_RESOURCES);
    }

    //
    // create a "blob" that AfpSetServerStatus can directly copy
    //
    TmpCount = 0;
    pCurrentBlob = AddrBlob;

    pList = DsiIpAddrList.Flink;
    while (pList != &DsiIpAddrList)
    {
        pIpaddrEntity = CONTAINING_RECORD(pList, IPADDRENTITY, Linkage);

        pCurrentBlob[0] = DSI_NETWORK_ADDR_LEN;
        pCurrentBlob[1] = DSI_NETWORK_ADDR_IPTAG;
        PUTDWORD2DWORD(&pCurrentBlob[2], pIpaddrEntity->IpAddress);

        pCurrentBlob += DSI_NETWORK_ADDR_LEN;

        pList = pList->Flink;

        TmpCount++;
        if (TmpCount == AddrCount)
        {
            break;
        }
    }

    RELEASE_SPIN_LOCK(&DsiAddressLock, OldIrql);

    *pIpAddrCount = AddrCount;
    *ppIpAddrBlob = AddrBlob;

    return(STATUS_SUCCESS);
}



/***	DsiGetIrpForTcp
 *
 *	This routine is called when we need to pass an irp back to TCP to get the
 *  remainint data (when it has more data than it has indicated to us).
 *  Here we allocate an mdl if there isn't one already, and allocate and
 *  initialize an irp ready to be sent to TCP
 *
 *  Parm IN:  pTcpConn - the connection object
 *            pBuffer - buffer that TCP will copy data in
 *            pInputMdl - if non-null, then we don't allocate a new mdl
 *            ReadSize - how many bytes do we need
 *
 *  Returns:  pIrp if successful, NULL otherwise
 *
 *  NOTE: pTcpConn spinlock is held on entry
 *
 */
PIRP
DsiGetIrpForTcp(
    IN  PTCPCONN    pTcpConn,
    IN  PBYTE       pBuffer,
    IN  PMDL        pInputMdl,
    IN  DWORD       ReadSize
)
{
    PDEVICE_OBJECT                  pDeviceObject;
    PIRP                            pIrp=NULL;
    PTDI_REQUEST_KERNEL_RECEIVE     pRequest;
    PMDL                            pMdl;

    pDeviceObject = IoGetRelatedDeviceObject(pTcpConn->con_pFileObject);

    pIrp = AfpAllocIrp(pDeviceObject->StackSize);
    if (pIrp == NULL)
    {
        DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_ERR,
            ("DsiGetIrpForTcp: alloc irp failed!\n"));

        return(NULL);
    }

    if (pInputMdl)
    {
        pMdl = pInputMdl;
    }
    else
    {
        pMdl = AfpAllocMdl(pBuffer, ReadSize, NULL);

        if (pMdl == NULL)
        {
            DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_ERR,
                ("DsiGetIrpForTcp: alloc mdl failed!\n"));

            AfpFreeIrp(pIrp);
            return(NULL);
        }
    }

    pTcpConn->con_pRcvIrp = pIrp;

    pTcpConn->con_State |= TCPCONN_STATE_TCP_HAS_IRP;

    // put TcpIRP refcount, removed when the irp completes
    pTcpConn->con_RefCount++;

    DBGREFCOUNT(("DsiGetIrpForTcp: TcpIRP inc %lx (%d  %d,%d)\n",
        pTcpConn,pTcpConn->con_RefCount,pTcpConn->con_State,pTcpConn->con_RcvState));

    TdiBuildReceive(pIrp,
                    pDeviceObject,
                    pTcpConn->con_pFileObject,
                    DsiTcpRcvIrpCompletion,
                    (PVOID)pTcpConn,
                    pMdl,
                    TDI_RECEIVE_NORMAL,
                    ReadSize);

    //
    // this irp will be returned to TCP, so do what IoSubSystem
    // would have done if we had called IoCallDriver
    //
    IoSetNextIrpStackLocation(pIrp);

    return(pIrp);
}



/***	DsiMakePartialMdl
 *
 *	This routine is called when we need to reissue an Mdl (via irp) back to TCP
 *  because TCP prematurely completed the previous irp (i.e. all the requested
 *  bytes haven't come in yet, but say Push bit was set or something).  In such
 *  a case, we need to give a new mdl which accounts for the bytes we have got
 *  so far (i.e. the offset has changed)
 *
 *  Parm IN:  pOrgMdl - the original Mdl we gave to TCp
 *            dwOffset - what offset we want the new partial Mdl to describe
 *
 *  Returns:  the new partial Mdl if successful, NULL otherwise
 *
 */
PMDL
DsiMakePartialMdl(
    IN  PMDL        pOrgMdl,
    IN  DWORD       dwOffset
)
{
    PMDL    pSubMdl;
    PMDL    pPartialMdl=NULL;
    DWORD   dwNewMdlLen;
    PVOID   vAddr;


    ASSERT(pOrgMdl != NULL);
    ASSERT(dwOffset > 0);

    ASSERT(dwOffset < AfpMdlChainSize(pOrgMdl));

    pSubMdl = pOrgMdl;

    //
    // get to the Mdl that is going to have this offset
    //
    while (dwOffset >= MmGetMdlByteCount(pSubMdl))
    {
        dwOffset -= MmGetMdlByteCount(pSubMdl);
        pSubMdl = pSubMdl->Next;

        ASSERT(pSubMdl != NULL);
    }

    ASSERT(MmGetMdlByteCount(pSubMdl) > dwOffset);

    vAddr = (PVOID)((PUCHAR)MmGetMdlVirtualAddress( pSubMdl ) + dwOffset);

    dwNewMdlLen = MmGetMdlByteCount(pSubMdl) - dwOffset;

    pPartialMdl = IoAllocateMdl(vAddr, dwNewMdlLen, FALSE, FALSE, NULL);

    if (pPartialMdl == NULL)
    {
        DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_ERR,
            ("DsiMakePartialMdl: IoAllocateMdl failed\n"));
        return(NULL);
    }

    AFP_DBG_INC_COUNT(AfpDbgMdlsAlloced);

    IoBuildPartialMdl(pSubMdl, pPartialMdl, vAddr, dwNewMdlLen);

    // if there are further Mdl's down the original, link them on
    pPartialMdl->Next = pSubMdl->Next;

    return(pPartialMdl);

}



/***	DsiUpdateAfpStatus
 *
 *	This routine is just a wrapper function so that we can schedule an event to
 *  call the real function AfpSetServerStatus
 *
 *  Returns:  status of operation
 *
 */
NTSTATUS FASTCALL
DsiUpdateAfpStatus(
    IN PVOID    Unused
)
{
    NTSTATUS            status;


    status = AfpSetServerStatus();

    return(status);
}



/***	DsiScheduleWorkerEvent
 *
 *	This routine schedules an event for a later time.  This routine is called
 *  typically when we are at dpc but something (e.g. file handle operations)
 *  needs to be done at passive level.  This routine puts the request on the
 *  worker queue.
 *
 *  Parm IN:  WorkerRoutine - the routine to be exececuted by the worker thread
 *            Context - parameter for that routine
 *
 *  Returns:  result of the operation
 *
 */
NTSTATUS
DsiScheduleWorkerEvent(
    IN  DSI_WORKER      WorkerRoutine,
    IN  PVOID           Context
)
{
    PTCPWORKITEM    pTWItem;

    pTWItem = (PTCPWORKITEM)AfpAllocZeroedNonPagedMemory(sizeof(TCPWORKITEM));
    if (pTWItem == NULL)
    {
        DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_ERR,
            ("DsiScheduleWorkerEvent: alloc failed!\n"));
        return(STATUS_INSUFFICIENT_RESOURCES);
    }

    AfpInitializeWorkItem(&pTWItem->tcp_WorkItem, DsiWorker, pTWItem);

    pTWItem->tcp_Worker = WorkerRoutine;
    pTWItem->tcp_Context = Context;

    AfpQueueWorkItem(&pTWItem->tcp_WorkItem);

    return(STATUS_SUCCESS);
}



/***	DsiScheduleWorkerEvent
 *
 *	This routine gets called by the worker thread when DsiScheduleWorkerEvent
 *  schedules an event.  This routine then calls the actual routine that was
 *  scheduled for later time.
 *
 *  Parm IN:  Context - the work item
 *
 *  Returns:  result of the operation
 *
 */
VOID FASTCALL
DsiWorker(
    IN PVOID    Context
)
{
    PTCPWORKITEM    pTWItem;
    NTSTATUS        status;

    pTWItem = (PTCPWORKITEM)Context;


    ASSERT (KeGetCurrentIrql() < DISPATCH_LEVEL);

    (*pTWItem->tcp_Worker)(pTWItem->tcp_Context);

    AfpFreeMemory(pTWItem);
}




/***	DsiShutdown
 *
 *	This routine is called when sfm is shutting down.  We basically make sure
 *  that all the resources are freed up, file handles closed etc.
 *
 *  Returns:  Nothing
 *
 */
VOID
DsiShutdown(
    IN VOID
)
{
    KIRQL           OldIrql;
    PLIST_ENTRY     pList;
    PIPADDRENTITY   pIpaddrEntity;
    PDSIREQ         pDsiReq=NULL;



    ACQUIRE_SPIN_LOCK(&DsiAddressLock, &OldIrql);

    while (!IsListEmpty(&DsiIpAddrList))
    {
        pList = RemoveHeadList(&DsiIpAddrList);

        pIpaddrEntity = CONTAINING_RECORD(pList, IPADDRENTITY, Linkage);

        AfpFreeMemory(pIpaddrEntity);
    }

    if (DsiStatusBuffer != NULL)
    {
        AfpFreeMemory(DsiStatusBuffer);
        DsiStatusBuffer = NULL;
    }

    RELEASE_SPIN_LOCK(&DsiAddressLock, OldIrql);

    // kill the global adapter if it's around
    if (DsiTcpAdapter)
    {
        KeClearEvent(&DsiShutdownEvent);

        DsiDestroyAdapter();

        // if the "adapter" is still hanging around, wait till it's gone
        if (DsiTcpAdapter)
        {
            DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_ERR,
                ("DsiShutdown: waiting for the TCP interface to go away...\n"));

            AfpIoWait(&DsiShutdownEvent, NULL);

            DBGPRINT(DBG_COMP_STACKIF, DBG_LEVEL_ERR,
                ("DsiShutdown: ... and the wait is over!\n"));
        }
    }

    ACQUIRE_SPIN_LOCK(&DsiResourceLock, &OldIrql);

    while (!IsListEmpty(&DsiFreeRequestList))
    {
        pList = RemoveHeadList(&DsiFreeRequestList);
        pDsiReq = CONTAINING_RECORD(pList, DSIREQ, dsi_Linkage);

        AfpFreeMemory(pDsiReq);
        DsiFreeRequestListSize--;
    }

    RELEASE_SPIN_LOCK(&DsiResourceLock, OldIrql);

    ASSERT(DsiFreeRequestListSize == 0);
}