/*++

 Copyright (c) 1998 Microsoft Corporation

 Module Name:    
       
    refcnt.c

 Abstract:       
    
    This module contains checked reference counting support functions.
    The free versions are inline.   
       
 Author:
 
       Scott Holden (sholden)  12/29/1998 Borrowed from IrDA.
       
 Revision History:

--*/

#ifdef NDIS40 // Only used for NDIS40 code now.
#if DBG

//
// Include Files
//

#include "dlc.h"
#include "llc.h"
#include "dbgmsg.h"


#define EXPAND_TAG(_Tag) ((CHAR *)(&_Tag))[0], \
                         ((CHAR *)(&_Tag))[1], \
                         ((CHAR *)(&_Tag))[2], \
                         ((CHAR *)(&_Tag))[3]

VOID
ReferenceInitDbg(
    IN PREF_CNT pRefCnt,
    PVOID       InstanceHandle,
    VOID        (*DeleteHandler)(PVOID pvContext),
    ULONG       TypeTag
    )

/*++

Routine Description:

    Initializes the reference control block. Reference count is initialized
    to zero.

Arguments:

    pRefCnt - pointer to uninitialized Reference Control Block
    
    InstanceHandle - handle to the managed instance.
    
    DeleteHandler - pointer to delete function, NULL is OK.
    
    TypeTag - Identifies initialization.

Return Value:

    The function's value is VOID.

--*/

{
    DEBUGMSG(DBG_REF, (TEXT("ReferenceInit(%#x, %#x, %#x, %c%c%c%c)\n"), 
        pRefCnt, InstanceHandle, DeleteHandler, EXPAND_TAG(TypeTag)));

    ASSERT(pRefCnt);

    //
    // Set the reference to 0 and save the instance 
    // handle and the delete handler.
    //

    pRefCnt->Count         = 0;
    pRefCnt->Instance      = InstanceHandle;
    pRefCnt->DeleteHandler = DeleteHandler;

    pRefCnt->Sig = REF_SIG;

    RtlZeroMemory(pRefCnt->Tags, sizeof(REF_TAG) * TAG_CNT);

    pRefCnt->Tags[0].Tag = 'LTOT';

    KeInitializeSpinLock(&pRefCnt->Lock);

    pRefCnt->TypeTag = TypeTag;

    return;
}

VOID
ReferenceAddDbg(
    PREF_CNT    pRefCnt, 
    ULONG       Tag,
    int         cLine
    )
{
    int             i;
    int             TotalPerArray = 0;
    KIRQL           OldIrql;

    ASSERT(pRefCnt->Sig == REF_SIG);

    DEBUGMSG(DBG_REF && DBG_VERBOSE, (TEXT("REFADD %#x [%c%c%c%c:%c%c%c%c] %d [l:%d]\n"),
        pRefCnt, EXPAND_TAG(pRefCnt->TypeTag), EXPAND_TAG(Tag), 
        pRefCnt->Count, cLine));    

    KeAcquireSpinLock(&pRefCnt->Lock, &OldIrql);

    for (i = 1; i < TAG_CNT; i++)
    {
        if (pRefCnt->Tags[i].Tag == 0 || pRefCnt->Tags[i].Tag == Tag)
        {
            pRefCnt->Tags[i].Tag = Tag;
            InterlockedIncrement(&pRefCnt->Tags[i].Count);
            break;
        }
    }

    ASSERT(i < TAG_CNT);

    InterlockedIncrement(&pRefCnt->Tags[0].Count);

    InterlockedIncrement(&pRefCnt->Count);

    ASSERT(pRefCnt->Tags[0].Count == pRefCnt->Count);    

#ifdef REFCNT_SANITY_CHECK    
    for (i = 1; i < TAG_CNT; i++)
    {
        if (pRefCnt->Tags[i].Tag != 0)
        {
            TotalPerArray += pRefCnt->Tags[i].Count;
            continue;
        }
    }

    ASSERT(TotalPerArray == pRefCnt->Tags[0].Count);
    
    if (TotalPerArray != pRefCnt->Tags[0].Count)
    {
        DbgBreakPoint();
    }        
#endif // REFCNT_SANITY_CHECK

    KeReleaseSpinLock(&pRefCnt->Lock, OldIrql);
}

VOID
ReferenceRemoveDbg(
    PREF_CNT pRefCnt, 
    ULONG    Tag,
    int      cLine)
{
    int             i;
    KIRQL           OldIrql;
    int             TotalPerArray = 0;
    BOOLEAN         FoundIt = FALSE;

    ASSERT(pRefCnt->Sig == REF_SIG);

    KeAcquireSpinLock(&pRefCnt->Lock, &OldIrql);

    DEBUGMSG(DBG_REF && DBG_VERBOSE, (TEXT("REFDEL %#x [%c%c%c%c:%c%c%c%c] %d [l:%d]\n"),
        pRefCnt, EXPAND_TAG(pRefCnt->TypeTag), EXPAND_TAG(Tag), 
        pRefCnt->Count, cLine));

    for (i = 1; i < TAG_CNT; i++)
    {
        if (pRefCnt->Tags[i].Tag == Tag)
        {
            FoundIt = TRUE;

            ASSERT(pRefCnt->Tags[i].Count > 0);

            InterlockedDecrement(&pRefCnt->Tags[i].Count);
            if (pRefCnt->Tags[i].Count == 0)
            {
                pRefCnt->Tags[i].Tag = Tag;
            }
            break;
        }
    }

    ASSERT(FoundIt);
    ASSERT(pRefCnt->Tags[0].Count > 0);
    ASSERT(pRefCnt->Tags[0].Count == pRefCnt->Count);

    InterlockedDecrement(&pRefCnt->Tags[0].Count);

    //
    // If the decremented count is non zero return the instance handle.
    //

    //
    // If reference is zero and delete handler is available, then call
    // handler.
    //

    if (InterlockedDecrement(&pRefCnt->Count) <= 0 &&
        pRefCnt->DeleteHandler)
    {
        DEBUGMSG(DBG_REF,(TEXT("REFDEL %#x [%c%c%c%c:%c%c%c%c] calling delete handler [l:%d].\n"),
            pRefCnt, EXPAND_TAG(pRefCnt->TypeTag), EXPAND_TAG(Tag), cLine));
        KeReleaseSpinLock(&pRefCnt->Lock, OldIrql);


        (pRefCnt->DeleteHandler)(pRefCnt->Instance);
    }
    else
    {
        KeReleaseSpinLock(&pRefCnt->Lock, OldIrql);
    }

#ifdef REFCNT_SANITY_CHECK    
    for (i = 1; i < TAG_CNT; i++)
    {
        if (pRefCnt->Tags[i].Tag != 0)
        {
            TotalPerArray += pRefCnt->Tags[i].Count;
            continue;
        }
    }
    
    ASSERT(TotalPerArray == pRefCnt->Tags[0].Count);
    
    if (TotalPerArray != pRefCnt->Tags[0].Count)
    {
        DbgPrint(TEXT("Tag %X, RefCnt %X, perArray %d, total %d\n"), Tag, pRefCnt,
                  TotalPerArray, pRefCnt->Tags[0].Count);
                  
        DbgBreakPoint();
    }    
#endif // REFCNT_SANITY_CHECK
}
#endif // DBG
#endif // NDIS40