/*++

Copyright (c) 1995-2000 Microsoft Corporation

Module Name:

    devres2.c

Abstract:

    Utility routines for resource matching

Author:

    Jamie Hunter (jamiehun) 9-July-1998

--*/

#include "precomp.h"
#pragma hdrstop

PRDE_LIST
pRDEList_Alloc()
/*++

Routine Description:

    Allocates a list-entry node

Arguments:

Return Value:

   PRDE_LIST entry

--*/
{
    PRDE_LIST Node;

    Node = (PRDE_LIST)MyMalloc(sizeof(RDE_LIST));
    if (Node == NULL) {
        return NULL;
    }
    Node->Prev = Node;
    Node->Next = Node;
    Node->Entry = NULL;
    return Node;
}

VOID
pRDEList_AddHead(
    IN OUT PRDE_LIST pList,
    IN PRDE_LIST Node
                 )
/*++

Routine Description:

    Adds a node to head of list

Arguments:

    pList =  pointer to list
    Node  =  node to add

Return Value:

   none

--*/
{
    MYASSERT(Node);
    MYASSERT(Node->Prev == Node);
    MYASSERT(Node->Next == Node);
    MYASSERT(pList);

    Node->Prev = pList;
    Node->Next = pList->Next;
    Node->Next->Prev = Node;
    pList->Next = Node; // Head
}

VOID
pRDEList_AddTail(
    IN OUT PRDE_LIST pList,
    IN PRDE_LIST Node
                 )
/*++

Routine Description:

    Adds a node to tail of list

Arguments:

    pList =  pointer to list
    Node  =  node to add

Return Value:

   none

--*/
{
    MYASSERT(Node);
    MYASSERT(Node->Prev == Node);
    MYASSERT(Node->Next == Node);
    MYASSERT(pList);

    Node->Next = pList;
    Node->Prev = pList->Prev;
    Node->Prev->Next = Node;
    pList->Prev = Node; // Tail
}

VOID
pRDEList_Remove(
    IN PRDE_LIST Node
                 )
/*++

Routine Description:

    Removes a node from list that node is member of

Arguments:

    Node  =  node to remove

Return Value:

   none

--*/
{
    MYASSERT(Node);

    if (Node->Prev == Node && Node->Next == Node) {
        //
        // already removed
        //
        return;
    }

    Node->Prev->Next = Node->Next;
    Node->Next->Prev = Node->Prev;
    Node->Next = Node;
    Node->Prev = Node;
}

PRDE_LIST
pRDEList_Find(
    IN PRDE_LIST pList,
    IN PRESDES_ENTRY pItem
    )
/*++

Routine Description:

    Looks for pItem in pList

Arguments:

    pList = list to search
    pItem = item to search for

Return Value:

   Node entry, or NULL

--*/
{
    PRDE_LIST Node;

    MYASSERT(pList);
    MYASSERT(pItem);

    Node = pList->Next; // head
    while (Node != pList) {
        if (Node->Entry == pItem) {
            return Node;
        }
        Node = Node->Next;
    }
    return NULL;
}


VOID
pRDEList_Destroy(
    IN PRDE_LIST pList
                 )
/*++

Routine Description:

    Destroy pList and everything on pList

Arguments:

    pList = list to destroy

Return Value:

   Node entry, or NULL

--*/
{
    PRDE_LIST Node,Next;

    if (pList == NULL) {
        return;
    }

    Node = pList; // head
    do
    {
        Next = Node->Next;
        MyFree(Node);       // this will free pList first, and then everything else on same list
        Node = Next;
    }
    while (Node != pList);
}

BOOL
pGetMatchingRange(
    IN ULONG64  ulKnownValue,
    IN ULONG64  ulKnownLen,
    IN LPBYTE   pData,
    IN RESOURCEID ResType,
    OUT PULONG  pRange,
    OUT PBOOL   pExact,
    OUT PULONG  pFlags
    )
/*++

Routine Description:

    Finds range index for resource inside ResDes

Arguments:

    ulKnownValue - base address
    ulKnownLen - length of resources
    pData/ResType - resource data we're comparing with
    pRange - output range index
    pExact - output true if there is only one range
    pFlags - output flags from matching range

Return Value:

   BOOL if match

--*/
{
    PGENERIC_RESOURCE pGenRes = NULL;
    ULONG64 ulValue = 0, ulLen = 0, ulEnd = 0;
    ULONG ulFlags = 0, i;

    pGenRes = (PGENERIC_RESOURCE)pData;

    for (i = 0; i < pGenRes->GENERIC_Header.GENERIC_Count; i++) {

        pGetRangeValues(pData, ResType, i, &ulValue, &ulLen, &ulEnd, NULL, &ulFlags);
        if (ulLen != ulKnownLen) {
            continue;
        }

        if ((ulKnownValue >= ulValue) &&
            ((ulKnownValue + ulLen - 1) <= ulEnd)) {

            if (pRange != NULL) {
                *pRange = i;
            }
            //
            // consider exact case
            //
            if (pExact != NULL) {
                if (pGenRes->GENERIC_Header.GENERIC_Count==1 && ulValue == ulKnownValue && (ulKnownValue + ulLen - 1) == ulEnd) {
                    *pExact = TRUE;
                }
            }
            if (pFlags != NULL) {
                //
                // want nearest flags
                //
                *pFlags = ulFlags;
            }

            return TRUE;
        }
    }
    if (pRange != NULL) {
        *pRange = 0;
    }
    return FALSE;
}


ULONG
pTryMatch(
    IN OUT PRESDES_ENTRY pKnown,
    IN OUT PRDE_LIST pResList,
    IN OUT PULONG pDepth
    )
/*++

Routine Description:

    Looks for best match of remaining requirements in
    remaining available resources
    returns number of matched requirements

Arguments:

    pKnown - requirements list (what is remaining)
    pResList - list of available resources

Return Value:

   Node entry, or NULL

--*/
{
    ULONG ThisBest = 0;
    ULONG MyBest = 0;
    PRDE_LIST pIterator = NULL;
    PRESDES_ENTRY pRange;
    ULONG64  ulValue, ulLen, ulEnd;
    ULONG  ulFlags;
    BOOL Exact = FALSE;
    ULONG Best = 0;
    PRDE_LIST pBestRes = NULL;
    BOOL BadMatch = FALSE;
    BOOL Prune = FALSE;
    BOOL NoRemaining = TRUE;

    MYASSERT(pDepth);
    *pDepth = 0;

    if (pKnown == NULL) {
        //
        // end recursion
        //
        return 0;
    }
    pKnown->CrossLink = NULL;

    //
    // we're looking for a match in pResList for pKnown
    // case (1) get a "Best" for if we decide not to match
    // case (2) get a "Best" for exact match, and chose between (1) and (2) if one exists
    // case (3) get a "Best" for each possible range match and choose best between (1) and all (3)
    //

    //
    // consider case(1) - what the results would be if we wasn't able to fit anything in
    //
    //Best = pTryMatch(pKnown->Next,pResList,pDepth);
    //pBestRes = NULL;

    pGetHdrValues(pKnown->ResDesData, pKnown->ResDesType, &ulValue, &ulLen, &ulEnd, &ulFlags);

    //
    // consider case(2) and (3) together
    //
    for(pIterator = pResList->Next;pIterator!=pResList;pIterator = pIterator->Next) {
        //
        // iterate through remaining resources
        //
        pRange = pIterator->Entry;
        if (pRange == NULL) {
            //
            // this has been used
            //
            continue;
        }
        if (pRange->ResDesType != pKnown->ResDesType) {
            //
            // not the kind of resource i'm looking for
            //
            continue;
        }
        NoRemaining = FALSE;
        if(pGetMatchingRange(ulValue, ulLen,pRange->ResDesData, pRange->ResDesType,NULL,&Exact,NULL)) {
            pIterator->Entry = NULL; // eliminate this range
            ThisBest = pTryMatch(pKnown->Next,pResList,pDepth); // match the rest, with us using this resource-range
            pIterator->Entry = pRange;
            if ((ThisBest > Best) || (pBestRes == NULL)) {
                //
                // the current best match (or first match if pBestRes == NULL)
                //
                pKnown->CrossLink = pRange;
                pBestRes = pIterator;
                Best = ThisBest;
                MyBest = 1;
                BadMatch = FALSE;
            } else {
                //
                // need to re-do best match
                //
                BadMatch = TRUE;
            }
            if (Exact || (*pDepth == ThisBest)) {
                //
                // prune - we're either exact, or got a perfect match
                //
                Prune = TRUE;
                goto Final;
            }
        }
    }

    if (NoRemaining) {
        //
        // I have no resources remaining I can use - consider this as good as a match
        // but we need to continue up the tree
        //
        Best = pTryMatch(pKnown->Next,pResList,pDepth); // match the rest, with us using this resource-range
        MyBest = TRUE;
        BadMatch = FALSE;
        goto Final;
    }

    //
    // if we get here we've:
    // (1) found a flexable match, but wasn't able to match everyone above us, or
    // (2) not found any match
    // note that if last best was n with us matching and this best is n+1 without
    // then we don't lose the best
    // consider if overall we'd do any better if we gave up our resources to someone else
    //
    if((pBestRes == NULL) || ((Best+MyBest) < *pDepth)) {
        //
        // if we had a match, only worth checking if we could increase best by more than MyBest
        // note that *pDepth is only valid if pBestRes != NULL
        //
        ThisBest = pTryMatch(pKnown->Next,pResList,pDepth);
        if ((ThisBest > (Best+MyBest)) || (pBestRes == NULL)) {
            //
            // the current best match
            //
            pKnown->CrossLink = NULL;
            pBestRes = NULL;
            Best = ThisBest;
            MyBest = 0;
            BadMatch = FALSE;
        } else {
            //
            // need to re-do best match
            //
            BadMatch = TRUE;
        }
    }

Final:

    if (BadMatch) {
        //
        // We had a bad-match since our last good match
        //
        if (pBestRes) {
            pRange = pBestRes->Entry; // the range we determined was our best bet
            pBestRes->Entry = NULL; // eliminate this range
            Best = pTryMatch(pKnown->Next,pResList,pDepth); // match the rest, with us using this resource-range
            pBestRes->Entry = pRange;
            pKnown->CrossLink = pRange;
            MyBest = 1;
        } else {
            Best = pTryMatch(pKnown->Next,pResList,pDepth); // match the rest, with us not using this resource range
            pKnown->CrossLink = NULL;
            MyBest = 0;
        }
    }

    //
    // if we found a match, we've saved it in pKnown->CrossLink
    // return Best+0 if it's better we don't fit our resource in, Best+1 otherwise
    // returns *pDepth = Best+1 if everyone (me up) fit their resources in
    //

    (*pDepth)++; // increment to include me
    return Best+MyBest; // MyBest = 1 if I managed to match my resource (or no resources left), Best = everyone to the right of me
}

ULONG
pMergeResDesDataLists(
    IN OUT PRESDES_ENTRY pKnown,
    IN OUT PRESDES_ENTRY pTest,
    OUT PULONG pMatchCount
    )
/*++

Routine Description:

    Map entries in pKnown into pTest
    as best as possible

Arguments:

    pKnown - list of known values
    pTest - list of ranges
    pMatchCount - set to be number of resources matched

Return Value:

    NO_LC_MATCH  if no correlation (not a single known matches)
    LC_MATCH_SUPERSET if at least one known matches, but some known's don't
    LC_MATCH_SUBSET if all known matches, but there are some range-items unmatched
    LC_MATCH if all known matches and all range-items match
    ORDERED_LC_MATCH if match, and match is in order

    pKnown->CrossLink entries point to matching entries in pTest
    pTest->CrossLink entries point to matching entries in pKnown

--*/
{
    PRDE_LIST pResList = NULL;
    PRDE_LIST Node;
    PRESDES_ENTRY pKnownEntry;
    PRESDES_ENTRY pTestEntry;
    ULONG Success = NO_LC_MATCH;
    ULONG Depth = 0;
    BOOL SomeKnownMatched = FALSE;
    BOOL SomeKnownUnMatched = FALSE;
    BOOL SomeTestMatched = FALSE;
    BOOL SomeTestUnMatched = FALSE;
    BOOL Ordered = TRUE;
    ULONG MatchCount = 0;

    if (pKnown == NULL) {
        goto Final;
    }

    //
    // reset
    //
    for(pKnownEntry = pKnown; pKnownEntry != NULL ;pKnownEntry = pKnownEntry->Next) {
        pKnownEntry->CrossLink = NULL;
    }

    for(pTestEntry = pTest; pTestEntry != NULL ;pTestEntry = pTestEntry->Next) {
        pTestEntry->CrossLink = NULL;
    }

    pResList = pRDEList_Alloc();

    if (pResList == NULL) {
        goto Final;
    }

    //
    // make all resources available
    // this gives us a work list without destroying original list
    //
    for(pTestEntry = pTest; pTestEntry != NULL ;pTestEntry = pTestEntry->Next) {
        Node = pRDEList_Alloc();

        if (Node == NULL) {
            goto Final;
        }
        Node->Entry = pTestEntry;
        pRDEList_AddTail(pResList,Node);
    }

    MatchCount = pTryMatch(pKnown,pResList,&Depth);

    if (MatchCount ==0) {
        //
        // no match
        //
        goto Final;
    }

    //
    // pKnown now has it's Cross-Link's set to determine success of this match
    //
    // consider NO_LC_MATCH, LC_MATCH_SUPERSET and ORDERED_LC_MATCH cases
    //

    pKnownEntry = pKnown;
    pTestEntry = pTest;

    while (pKnownEntry) {
        if (pKnownEntry->CrossLink == NULL) {
            SomeKnownUnMatched = TRUE;
        } else {
            SomeKnownMatched = TRUE;    // we have at least one matched
            pKnownEntry->CrossLink->CrossLink = pKnownEntry; // cross-link test entries
            if (pKnownEntry->CrossLink != pTestEntry) {
                Ordered = FALSE;        // ordered compare lost
            } else {
                pTestEntry = pTestEntry->Next; // goto next test for ordered
            }
        }
        pKnownEntry = pKnownEntry->Next;
    }
    if (Ordered && pTestEntry != NULL) {
        Ordered = FALSE;
    }

    if (SomeKnownUnMatched) {
        if (SomeKnownMatched) {
            Success = LC_MATCH_SUPERSET;
        }
        goto Final;
    }

    if (Ordered) {
        Success = ORDERED_LC_MATCH;
        goto Final;
    }

    //
    // consider between LC_MATCH_SUBSET and LC_MATCH
    //
    pTestEntry = pTest;

    while (pTestEntry) {
        if (pTestEntry->CrossLink == NULL) {
            //
            // the first NULL CrossLink entry makes Known a Subset of Test
            Success = LC_MATCH_SUBSET;
            goto Final;
        }
        pTestEntry = pTestEntry->Next;
    }
    //
    // if we get here, there is an exact match
    //
    Success = LC_MATCH;

  Final:
    pRDEList_Destroy(pResList);

    if (pMatchCount != NULL) {
        *pMatchCount = MatchCount;
    }

    return Success;
}

ULONG
pCompareLogConf(
    IN LOG_CONF KnownLogConf,
    IN LOG_CONF TestLogConf,
    IN HMACHINE hMachine,
    OUT PULONG pMatchCount
    )

/*++

Routine Description:

    This routine compares two log confs and returns info about how well
    they match.
    This simply uses the pMergeResDesDataLists function to get match status

Arguments:

   KnownLogConf = First log conf to compare (fixed values)
   TestConfType = Second log conf to compare (range values)
   hMachine = Machine to compare on
   pMatchCount = number of resources matched

Return Value:

    As pMergeResDesDataLists

--*/

{
    PRESDES_ENTRY pKnownResList = NULL, pTestResList = NULL;
    ULONG Status;

    //
    // Retrieve the resources for each log conf
    //

    if (!pGetResDesDataList(KnownLogConf, &pKnownResList, TRUE,hMachine)) {
        if (pMatchCount) {
            *pMatchCount = 0;
        }
        return NO_LC_MATCH;
    }

    if (!pGetResDesDataList(TestLogConf, &pTestResList, TRUE,hMachine)) {
        pDeleteResDesDataList(pKnownResList);
        if (pMatchCount) {
            *pMatchCount = 0;
        }
        return NO_LC_MATCH;
    }

    Status = pMergeResDesDataLists(pKnownResList,pTestResList,pMatchCount);

    pDeleteResDesDataList(pKnownResList);
    pDeleteResDesDataList(pTestResList);

    return Status;
}

BOOL
pFindMatchingAllocConfig(
    IN  LPDMPROP_DATA lpdmpd
    )
{
    HWND     hDlg = lpdmpd->hDlg;
    ULONG    ulBasicLC = 0;
    ULONG    ulBasicCount = 0;
    BOOL     bFoundCorrectLC = FALSE;
    LOG_CONF LogConf;
    ULONG    lastMatchStatus = NO_LC_MATCH, bestMatchStatus = NO_LC_MATCH;
    UINT lastMatchCount;
    UINT bestMatchCount = 0;
    HMACHINE hMachine;

    hMachine = pGetMachine(lpdmpd);

    lpdmpd->MatchingLC = (LOG_CONF)0;
    lpdmpd->MatchingLCType = BASIC_LOG_CONF;

    //
    // Load the values associated with the allocated config in the list box,
    // but associate each with the resource requirements descriptor that it
    // originated from. To do this, we have to match the allocated config
    // with the basic/filtered config it is based on.
    //
    // NOTE: if we got here, then we know that an known config of some kind
    // exists (passed in as param) and that at least one basic/filtered config
    // exists. Further more, we know that the combobox has already been
    // filled in with a list of any basic/filtered configs and the lc handle
    // associated with them.
    //

    ulBasicCount = (ULONG)SendDlgItemMessage(hDlg,IDC_DEVRES_LOGCONFIGLIST,CB_GETCOUNT,
                                                        (WPARAM)0,(LPARAM)0);
    if (ulBasicCount == (ULONG)LB_ERR) {
        return FALSE;
    }

    for (ulBasicLC = 0 ; ulBasicLC < ulBasicCount; ulBasicLC++) {
        //
        // Retrieve the log conf handle
        //

        LogConf = (LOG_CONF)SendDlgItemMessage(hDlg, IDC_DEVRES_LOGCONFIGLIST,
                                               CB_GETITEMDATA, ulBasicLC, 0L);

        if (LogConf != 0) {
            //
            // Determine how good a match this requirements list is.
            //

            lastMatchStatus = pCompareLogConf(lpdmpd->CurrentLC, LogConf,hMachine,&lastMatchCount);
            if ((lastMatchStatus > bestMatchStatus)
                ||  ((bestMatchStatus == lastMatchStatus) && lastMatchCount > bestMatchCount)) {
                bestMatchCount = lastMatchCount;
                bestMatchStatus =lastMatchStatus;
                lpdmpd->MatchingLC = LogConf;
            }
        }
    }

    if (bestMatchStatus == NO_LC_MATCH || bestMatchStatus == LC_MATCH_SUBSET) {
        //
        // this doesn't follow any valid config
        //
        return FALSE;
    }

    lpdmpd->dwFlags &= ~(DMPROP_FLAG_PARTIAL_MATCH|DMPROP_FLAG_MATCH_OUT_OF_ORDER);
    if (bestMatchStatus != ORDERED_LC_MATCH) {
        //
        // If match status isn't ORDERED_LC_MATCH, then ordering of the resource descriptors
        // didn't match up.  Set a flag to indicate this, so that later on we'll know to handle
        // this specially.
        //
        lpdmpd->dwFlags |= DMPROP_FLAG_MATCH_OUT_OF_ORDER;
    } else if (bestMatchStatus < LC_MATCH) {
        //
        // match is partial
        //
        lpdmpd->dwFlags |= DMPROP_FLAG_PARTIAL_MATCH;
    }
    return TRUE;

} // LoadMatchingAllocConfig

BOOL
pGetMatchingResDes(
    IN ULONG64    ulKnownValue,
    IN ULONG64    ulKnownLen,
    IN ULONG64    ulKnownEnd,
    IN RESOURCEID ResType,
    IN LOG_CONF   MatchingLogConf,
    OUT PRES_DES  pMatchingResDes,
    IN HMACHINE   hMachine
    )
/*++

Routine Description:

    This returns a res des that matches the specified values.
    used by pSaveCustomResSettings

Arguments:

    ulKnownValue    Starting resource value to match against

    ulKnownLen      Length of resource to match against

    ResType         Type of resource to match against

    MatchnigLogConf Log conf to retreive potential matching res des from

    pMatchingResDes Supplies a pointer that on return contains a matching
                    res des if any.

Return Value:

   None.

--*/

{
    CONFIGRET   Status = CR_SUCCESS;
    RESOURCEID  Res;
    RES_DES     ResDes, ResDesTemp;
    ULONG64     ulValue = 0, ulLen = 0, ulEnd = 0;
    ULONG       ulSize, ulFlags = 0, i;
    PGENERIC_RESOURCE   pGenRes;
    BOOL        bMatch = FALSE;
    LPBYTE      pData = NULL;


    //
    // The MatchingLogConf is a requirements list. Loop through each res des
    // in the matching log conf until we find a res des that matches the
    // known res des values.
    //
    Status = CM_Get_Next_Res_Des_Ex(&ResDes, MatchingLogConf, ResType, &Res, 0,hMachine);

    while (Status == CR_SUCCESS) {
        //
        // Get res des data
        //
        if (CM_Get_Res_Des_Data_Size_Ex(&ulSize, ResDes, CM_RESDES_WIDTH_64,hMachine) != CR_SUCCESS) {
            CM_Free_Res_Des_Handle(ResDes);
            break;
        }

        pData = MyMalloc(ulSize);
        if (pData == NULL) {
            CM_Free_Res_Des_Handle(ResDes);
            break;
        }

        if (CM_Get_Res_Des_Data_Ex(ResDes, pData, ulSize, DEVRES_WIDTH_FLAGS,hMachine) != CR_SUCCESS) {
            CM_Free_Res_Des_Handle(ResDes);
            MyFree(pData);
            break;
        }

        if(pGetMatchingRange(ulKnownValue,ulKnownLen,pData,ResType,NULL,NULL,NULL)) {
            *pMatchingResDes = ResDes;
            bMatch = TRUE;
            MyFree(pData);
            goto MatchFound;
        }

        //
        // Get next res des in log conf
        //
        ResDesTemp = ResDes;
        Status = CM_Get_Next_Res_Des_Ex(&ResDes, ResDesTemp,
                                     ResType, &Res, 0,hMachine);

        CM_Free_Res_Des_Handle(ResDesTemp);
        MyFree(pData);
    }

    MatchFound:

    return bMatch;

} // GetMatchingResDes



//
// NTRAID#166214-2000/08/19-JamieHun Conflict Supression Hack
// this stuff needs to be fixed proper
//

PTSTR
pGetRegString(
    IN HKEY hKey,
    IN PCTSTR regval
    )
/*++

Routine Description:

    Obtain and return a registry string allocated by MyMalloc
    return NULL if we couldn't retrieve string

Arguments:

    hKey - key to retrieve string from
    regval - value to retrieve

Return Value:

    copy of registry string, may be free'd with MyFree

--*/
{
    DWORD dwSize;
    DWORD dwType;
    PTSTR pSz;
    LONG res;

    dwType = 0;
    dwSize = 0;
    res = RegQueryValueEx(hKey,regval,NULL,&dwType,(PBYTE)NULL,&dwSize);
    if (res != ERROR_SUCCESS) {
        return NULL;
    }
    if (dwType != REG_SZ) {
        return NULL;
    }
    pSz = MyMalloc(dwSize);
    if (pSz == NULL) {
        return NULL;
    }
    res = RegQueryValueEx(hKey,regval,NULL,&dwType,(PBYTE)pSz,&dwSize);
    if (res != ERROR_SUCCESS) {
        MyFree(pSz);
        return NULL;
    }
    return pSz;
}

VOID
pFillCETags(
    IN PCONFLICT_EXCEPTIONS pExceptions,
    IN PCE_TAGS pTags,
    PTSTR pSz
    )
/*++

Routine Description:

    parse a list of tags into CE_TAGS structure
    adding the strings into the string table en-route
    note that this structure will be flexable and allow ',' or ';' seperator
    however when used in Exceptions string, we've already eliminated any ';'

    format is:
    <tag>,<tag>,<tag> or <tag>;<tag>;<tag>

Arguments:

    pExceptions - context information
    pTags - tag structure to fill in

Return Value:

    none

--*/
{
    static CE_TAGS DummyEntry = { -1 }; // if we write a new string, negative size count means this isn't a devnode entry

    MYASSERT(pTags->nTags == 0);

    while(pSz[0] && pTags->nTags < MAX_CE_TAGS) {
        if(pSz[0]==TEXT(',')||pSz[0]==TEXT(';')||pSz[0]<=TEXT(' ')) {
            pSz++;
        } else {
            PTSTR pOldSz = pSz;
            PTSTR pLastSpace = NULL;
            LONG id;
            while (pSz[0] && pSz[0]!=TEXT(';')&& pSz[0]!=TEXT(',')) {
                if (pSz[0]<=TEXT(' ')) {
                    if (pLastSpace==NULL) {
                        pLastSpace = pSz;
                    }
                } else {
                    pLastSpace = NULL;
                }
                pSz++;
            }
            //
            // pSz points to '\0', ';' or ','
            // pLastSpace points to any trailing WS
            // pOldSz points to start of string
            //
            if(pLastSpace==NULL) {
                pLastSpace = pSz;
            }
            if (pSz[0]) {
                pSz++;
            }
            pLastSpace[0]=TEXT('\0');
            //
            // pSz points to next string, pOldSz points to this string
            // add string to string table, place in list of tags
            //
            id = pSetupStringTableAddStringEx(pExceptions->ceTagMap,pOldSz,STRTAB_CASE_INSENSITIVE|STRTAB_BUFFER_WRITEABLE,&DummyEntry,sizeof(DummyEntry));
            if (id>=0) {
                pTags->Tag[pTags->nTags++] = id;
            }
        }
    }
}

PCE_ENTRY
pScanConflictExceptionEntry(
    IN PCONFLICT_EXCEPTIONS pExceptions,
    PTSTR pSz
    )
/*++

Routine Description:

    obtains conflict exception info from string

    format is:
    (1)  <tags>          - always ignore tag for any type of conflict
    (2)  <rt>:<tags>     - ignore tag for <rt> resource type
    (3)  <rt>@x:<tags>   - IRQ/DMA - specfic
    (4)  <rt>@x-y:<tags> - IO/MEM - range

    <tags>  are a comma-sep list of tags <tag>,<tag>,<tag>

Arguments:

    pExceptions - context information
    pSz - string to parse

Return Value:

    CE_ENTRY structure if this is a valid descriptor

--*/
{
    PTSTR brk;
    PCE_ENTRY pEntry;
    TCHAR rt[5];
    int c;

    while (pSz[0] && pSz[0]<=TEXT(' ')) {
        pSz++;
    }
    if (!pSz[0]) {
        return NULL;
    }

    pEntry = MyMalloc(sizeof(CE_ENTRY));

    if (pEntry == NULL) {
        return NULL;
    }
    ZeroMemory(pEntry,sizeof(CE_ENTRY));

    brk = _tcschr(pSz,TEXT(':'));

    if(!brk) {
        //
        // treat as tags only
        //
        pEntry->resType = ResType_None;
    } else {
        //
        // determine resource type
        //
        for(c=0;_istalpha(pSz[0]) && c<(sizeof(rt)/sizeof(TCHAR)-1);c++,pSz++) {
            rt[c] = (TCHAR)_totupper(pSz[0]);
        }
        rt[c] = 0;
        while (pSz[0] && pSz[0]<=TEXT(' ')) {
            pSz++;
        }
        if (pSz[0]!=TEXT(':') && pSz[0]!=TEXT('@')) {
            MyFree(pEntry);
            return NULL;
        } else if (lstrcmp(rt,CE_RES_IO)==0) {
            pEntry->resType = ResType_IO;
        } else if (lstrcmp(rt,CE_RES_MEM)==0) {
            pEntry->resType = ResType_Mem;
        } else if (lstrcmp(rt,CE_RES_IRQ)==0) {
            pEntry->resType = ResType_IRQ;
        } else if (lstrcmp(rt,CE_RES_DMA)==0) {
            pEntry->resType = ResType_DMA;
        } else {
            MyFree(pEntry);
            return NULL;
        }
        if (pSz[0]!=TEXT('@')) {
            //
            // no range follows
            //
            pEntry->resStart = (ULONG64)0;
            pEntry->resEnd = (ULONG64)(-1);
        } else {
            //
            // @x[-y]:
            //
            ULONG x;
            ULONG y;
            PTSTR i;

            pSz++; // past @

            while (pSz[0] && pSz[0]<=TEXT(' ')) {
                pSz++;
            }
            i = pSz;
            x = _tcstoul(pSz,&i,0);
            if (i==pSz) {
                MyFree(pEntry);
                return NULL;
            }
            pSz = i;
            while (pSz[0] && pSz[0]<=TEXT(' ')) {
                pSz++;
            }
            if (pSz[0]==TEXT('-')) {
                //
                // -y
                //
                pSz++;
                while (pSz[0] && pSz[0]<=TEXT(' ')) {
                    pSz++;
                }
                i = pSz;
                y = _tcstoul(pSz,&i,0);
                if (i==pSz || y<x) {
                    MyFree(pEntry);
                    return NULL;
                }
                pSz = i;
                while (pSz[0] && pSz[0]<=TEXT(' ')) {
                    pSz++;
                }
            } else {
                y = x;
            }
            pEntry->resStart = x;
            pEntry->resEnd = y;
        }
        if (pSz[0] != TEXT(':')) {
            MyFree(pEntry);
            return NULL;
        }
        pSz ++; // skip past colon
    }
    //
    // at this point, expect a list of tags
    // each tag terminated by a comma
    //
    pFillCETags(pExceptions,&pEntry->tags,pSz);
    if (!pEntry->tags.nTags) {
        MyFree(pEntry);
        return NULL;
    }
    return pEntry;
}

PCONFLICT_EXCEPTIONS pLoadConflictExceptions(
    IN LPDMPROP_DATA lpdmpd
    )
/*++

Routine Description:

    Load the string "ResourcePickerExceptions" if any
    create a context structure for eliminating false conflicts
    this is one huge hack.

Arguments:

    lpdmpd - Context data

Return Value:

    CONFLICT_EXCEPTIONS structure, if "active" contains a string table and a list of resource exceptions

--*/
{
    PCONFLICT_EXCEPTIONS pExceptions;
    PCE_ENTRY pEntry;
    BOOL bStatus;
    HKEY hDevRegKey;
    PTSTR pSz;
    PTSTR pScanSz;
    PTSTR pOldSz;
    PCE_TAGS pTags;

    //
    // we always create the structure, so we will avoid looking for registry info every time
    //
    pExceptions = MyMalloc(sizeof(CONFLICT_EXCEPTIONS));
    if (pExceptions == NULL) {
        return NULL;
    }
    ZeroMemory(pExceptions,sizeof(CONFLICT_EXCEPTIONS));

    hDevRegKey = SetupDiOpenDevRegKey(lpdmpd->hDevInfo,lpdmpd->lpdi,DICS_FLAG_GLOBAL,0,DIREG_DRV,KEY_READ);
    if (hDevRegKey == INVALID_HANDLE_VALUE) {
        //
        // can't find key, no conflict elimination
        //
        return pExceptions;
    }
    pSz = pGetRegString(hDevRegKey,REGSTR_VAL_RESOURCE_PICKER_EXCEPTIONS);
    RegCloseKey(hDevRegKey);
    if(pSz == NULL) {
        //
        // can't find key, no conflict elimination
        //
        return pExceptions;
    }

    //
    // now parse the string creating our context to eliminate false conflicts
    //

    pExceptions->ceTagMap = pSetupStringTableInitializeEx(sizeof(CE_TAGS),0);
    if (pExceptions->ceTagMap == NULL) {
        MyFree(pSz);
        return pExceptions;
    }

    pScanSz = pSz;

    while (pScanSz[0]) {
        if (pScanSz[0] == TEXT(';')) {
            pScanSz ++;
        } else {
            pOldSz = pScanSz;
            while (pScanSz[0] && pScanSz[0] != TEXT(';')) {
                pScanSz++;
            }
            if (pScanSz[0]) {
                pScanSz[0] = 0;
                pScanSz++;
            }
            pEntry = pScanConflictExceptionEntry(pExceptions,pOldSz);
            if (pEntry) {
                pEntry->Next = pExceptions->exceptions;
                pExceptions->exceptions = pEntry;
            }
        }
    }

    MyFree(pSz);
    return pExceptions;
}

VOID pFreeConflictExceptions(
    IN PCONFLICT_EXCEPTIONS pExceptions
    )
/*++

Routine Description:

    Releases memory used by PCONFLICT_EXCEPTIONS

Arguments:

    pExceptions     structure to release

Return Value:

   None.

--*/
{
    //
    // free the list
    //
    while (pExceptions->exceptions) {
        PCE_ENTRY pEntry = pExceptions->exceptions;
        pExceptions->exceptions = pEntry->Next;
        MyFree(pEntry);
    }
    //
    // free the string table
    //
    if (pExceptions->ceTagMap) {
        pSetupStringTableDestroy(pExceptions->ceTagMap);
    }
    MyFree(pExceptions);
}

BOOL pIsConflictException(
    IN LPDMPROP_DATA lpdmpd,
    IN PCONFLICT_EXCEPTIONS pExceptions,
    IN DEVINST devConflict,
    IN PCTSTR resDesc,
    IN RESOURCEID resType,
    IN ULONG64 resValue,
    IN ULONG64 resLength
    )
/*++

Routine Description:

    Load the string "ResourcePickerExceptions" if any
    create a context structure for eliminating false conflicts
    this is one huge hack.

Arguments:

    lpdmpd - Context data
    pExceptions - Cache of information
    devConflict - DEVINST that's shown to be conflicting with us, -1 if "unavailable" (tag = *)
    resType - type of resource that we tested
    resValue - value of resource that we tested
    resLength - length of resource that we tested

Return Value:

    TRUE if this is an exception

--*/
{
    HMACHINE hMachine;
    TCHAR DevNodeName[MAX_DEVNODE_ID_LEN];
    CE_TAGS tags;
    PCE_ENTRY pEntry;
    LONG tagent;
    LONG n,m;
    ULONG64 resEnd = resValue+(resLength-1);
    PTSTR pSz;
    HKEY hKey;

    //
    // if we're not doing any exceptions, get out ASAP
    //
    if (pExceptions->exceptions == NULL) {
        return FALSE;
    }

    hMachine = pGetMachine(lpdmpd);
    //
    // handle "reserved" case first
    //
    if (devConflict != -1) {
        //
        // obtain device instance string
        //
        if(CM_Get_Device_ID_Ex(devConflict,DevNodeName,MAX_DEVNODE_ID_LEN,0,hMachine)!=CR_SUCCESS) {
            devConflict = -1;
        }
    }
    if (devConflict == -1) {
        if (resDesc && resDesc[0]) {
            lstrcpy(DevNodeName,resDesc);
        } else {
            lstrcpy(DevNodeName,CE_TAG_RESERVED);
        }
    } else {
    }
    //
    // is this a brand-new devnodename ?
    //
    tags.nTags = 0;
    tagent = pSetupStringTableLookUpStringEx(pExceptions->ceTagMap,DevNodeName,STRTAB_CASE_INSENSITIVE|STRTAB_BUFFER_WRITEABLE,&tags,sizeof(tags));
    if(tagent<0 || tags.nTags<0) {
        //
        // this particular devnode hasn't been processed before, ouch time
        //
        ZeroMemory(&tags,sizeof(tags)); // default reserved case
        if (devConflict != -1) {
            //
            // we need to get regkey for this devnode
            // I could do this via setupapi, or cfgmgr
            // for efficiency, I'm going latter route
            //
            if(CM_Open_DevNode_Key_Ex(devConflict,
                     KEY_READ,
                     0,
                     RegDisposition_OpenExisting,
                     &hKey,
                     CM_REGISTRY_SOFTWARE,
                     hMachine) == CR_SUCCESS) {

                pSz = pGetRegString(hKey,REGSTR_VAL_RESOURCE_PICKER_TAGS);
                RegCloseKey(hKey);

                if (pSz) {
                    //
                    // now fill in tags
                    //
                    pFillCETags(pExceptions,&tags,pSz);
                    MyFree(pSz);
                }
            }
        }
        //
        // now write this back into the string table
        // this time, non-negative nTags indicates we've processed this once
        // we will re-write the extra-data
        //
        tagent = pSetupStringTableAddStringEx(pExceptions->ceTagMap,DevNodeName,STRTAB_CASE_INSENSITIVE|STRTAB_BUFFER_WRITEABLE|STRTAB_NEW_EXTRADATA,&tags,sizeof(tags));
    }
    if (tagent<0) {
        //
        // if this happens, we have other problems (out of memory)
        //
        return FALSE;
    }

    //
    // go through our list of exceptions
    // an exception that mentions tagent, or any of tags associated with tagent (in tags) is rejected
    // the policy in this routine is make this an exception if we can
    //

    for(pEntry=pExceptions->exceptions;pEntry;pEntry=pEntry->Next) {

        if (pEntry->resType != ResType_None) {
            //
            // we need to validate the resource
            //
            if (pEntry->resType != resType ||
                    pEntry->resStart > resValue ||
                    pEntry->resEnd < resEnd) {
                continue;
            }
        }
        for (n=0;n<pEntry->tags.nTags;n++) {
            if (pEntry->tags.Tag[n] == tagent) {
                MYTRACE((DPFLTR_ERROR_LEVEL, TEXT("Setup: Eliminated false conflict with %s type=%u, start=0x%08x, len=0x%08x\n"),DevNodeName,resType,resValue,resLength));
                return TRUE;    // hit (devnode itself, where devnode may also be "*")
            }
            for (m=0;m<tags.nTags;m++) {
                if (pEntry->tags.Tag[n] == tags.Tag[m]) {
                    MYTRACE((DPFLTR_ERROR_LEVEL, TEXT("Setup: Eliminated false conflict with %s (via tag %s) type=%u, start=0x%08x, len=0x%08x\n"),DevNodeName,pStringTableStringFromId(pExceptions->ceTagMap,tags.Tag[m]),resType,resValue,resLength));
                    return TRUE;    // hit on one of associated tags
                }
            }
        }
    }

    return FALSE;
}


