#include #include #include #include #include #include #include #include #include "netinfo.h" // Globals DEV_INFO *g_pdiDevList; // Head of device list /*++ Routine Description: (21) GetDevNodeInfoAndCreateNewDevInfoNode Creates new list node, then gets registry and resource information for a specific device and copies it into that node. Finally, adds new node to beginning of linked list Arguments: dnDevNode: the device to find information about szDevNodeID: the registry path name of the device szEnumBuffer: name of enumerator this device is under Return Value: BOOL: TRUE if function succeeds, FALSE if not --*/ BOOL GetDevNodeInfoAndCreateNewDevInfoNode(IN DEVNODE dnDevNode, IN PTCHAR szDevNodeID, IN PTCHAR szEnumBuffer) { LOG_CONF lcLogConf = 0, lcLogConfNew; CONFIGRET cmret, cmret2; PDEV_INFO pdiDevInfo=(PDEV_INFO)malloc(sizeof(DEV_INFO)); int i; BOOL boolForcedFound = FALSE, boolAllocFound = FALSE; USHORT ushLogConfType[4] = {BOOT_LOG_CONF, ALLOC_LOG_CONF, BASIC_LOG_CONF, FORCED_LOG_CONF}; if (pdiDevInfo == NULL) goto RetFALSE; // If this is not a PnP device, skip it if (!lstrcmpi(szEnumBuffer, TEXT("Root"))) { free(pdiDevInfo); goto RetTRUE; } // // Initialize fields inside the node // if (!InitializeInfoNode(pdiDevInfo, szDevNodeID, dnDevNode)) { // This is a device we don't want to list. Skip it free(pdiDevInfo); goto RetTRUE; } for (i = 0; i < NUM_LOG_CONF_TYPES; i++) { // Get logical configuration information cmret = CM_Get_First_Log_Conf(&lcLogConfNew, dnDevNode, ushLogConfType[i]); while (CR_SUCCESS == cmret) { lcLogConf = lcLogConfNew; if (ALLOC_LOG_CONF == ushLogConfType[i]) { boolAllocFound = TRUE; } if (!(GetResDesList(pdiDevInfo, lcLogConf, ushLogConfType[i]))) { goto RetFALSE; } cmret = CM_Get_Next_Log_Conf(&lcLogConfNew, lcLogConf, 0); cmret2 = CM_Free_Log_Conf_Handle(lcLogConf); } } // // If device has no Alloc configurations, skip // to the next device // if (!boolAllocFound) { //free(pdiDevInfo); //goto RetTRUE; } // // Insert new pdiDevInfo into Linked List of DevNodes // if (g_pdiDevList == NULL) { // // DevList is empty // g_pdiDevList = pdiDevInfo; } else { // // Add new pdiDevInfo to beginning of linked list // pdiDevInfo->Next = g_pdiDevList; g_pdiDevList->Prev = pdiDevInfo; g_pdiDevList = pdiDevInfo; } RetTRUE: return TRUE; RetFALSE: return FALSE; } /*++ Routine Description: (20) ParseEnumerator Gets devices listed under enumerator name in registry Arguments: szEnumBuffer: the enumerator name Return Value: BOOL: TRUE if function succeeds, FALSE if not --*/ BOOL ParseEnumerator(IN PTCHAR szEnumBuffer) { PTCHAR szDevIDBuffer = NULL; PTCHAR szDevNodeID = NULL; ULONG ulDevIDBufferLen = 0, ulCount = 0, ulStart = 0; CONFIGRET cmret = CR_SUCCESS; DEVNODE dnDevNode; BOOL bReturn = TRUE; // // Get buffer length // cmret = CM_Get_Device_ID_List_Size(&ulDevIDBufferLen, szEnumBuffer, CM_GETIDLIST_FILTER_ENUMERATOR); if (CR_SUCCESS != cmret) { //ErrorLog(20, TEXT("CM_Get_Device_ID_List_Size"), cmret, NULL); bReturn = FALSE; goto RetFALSE; } if ((szDevIDBuffer =(PTCHAR) malloc(sizeof(TCHAR) * ulDevIDBufferLen)) == NULL || (szDevNodeID =(PTCHAR) malloc(sizeof(TCHAR) * ulDevIDBufferLen)) == NULL) { bReturn = FALSE; goto RetFALSE; } // // Get the Device ID List // cmret = CM_Get_Device_ID_List(szEnumBuffer, szDevIDBuffer, ulDevIDBufferLen, CM_GETIDLIST_FILTER_ENUMERATOR); if (CR_SUCCESS != cmret) { //ErrorLog(20, TEXT("CM_Get_Device_ID_List"), cmret, NULL); bReturn = FALSE; goto RetFALSE; } // // Note that ulDevIDBufferLen is a loose upper bound. The API may have // returned a size greater than the actual size of the list of strings. // for (ulCount = 0; ulCount < ulDevIDBufferLen; ulCount++) { ulStart = ulCount; if (szDevIDBuffer[ulCount] != '\0') { cmret = CM_Locate_DevNode(&dnDevNode, szDevIDBuffer + ulCount, CM_LOCATE_DEVNODE_NORMAL); // // Go to the next substring // while (szDevIDBuffer[ulCount] != TEXT('\0')) { ulCount++; } // Stop when we reach the double-NULL terminator if (szDevIDBuffer[ulCount+1] == TEXT('\0')) { ulCount=ulDevIDBufferLen; continue; } if (cmret == CR_SUCCESS) { _tcsncpy(szDevNodeID, szDevIDBuffer + ulStart, ulDevIDBufferLen); // Found the DevNode, so add its information to the device list if (!(GetDevNodeInfoAndCreateNewDevInfoNode(dnDevNode, szDevNodeID, szEnumBuffer))) { bReturn = FALSE; goto RetFALSE; } } } } bReturn = TRUE; RetFALSE: // fix to rajeshm old code. Selective free bug number 137987 if (NULL != szDevIDBuffer) free (szDevIDBuffer); if (NULL != szDevNodeID) free (szDevNodeID); return bReturn; } void CollectDevData() { CONFIGRET cmret = CR_SUCCESS; ULONG ulIndexNum = 0; ULONG ulEnumBufferLen = 0; PTCHAR szEnumBuffer; szEnumBuffer = (PTCHAR) malloc(sizeof(TCHAR) * MAX_DEVNODE_ID_LEN); if (NULL == szEnumBuffer) return; for (ulIndexNum = 0; cmret == CR_SUCCESS; ulIndexNum++) { ulEnumBufferLen = MAX_DEVNODE_ID_LEN; cmret = CM_Enumerate_Enumerators( ulIndexNum, szEnumBuffer, &ulEnumBufferLen, 0); if (cmret == CR_SUCCESS) ParseEnumerator(szEnumBuffer); } free (szEnumBuffer); } BOOL CopyRegistryLine(IN DEVNODE dnDevNode, IN ULONG ulPropertyType, IN PDEV_INFO pdiDevInfo) /*++ Routine Description: (22) CopyRegistryLine Copies one specific string of registry data to new list node Arguments: dnDevNode: the device to get information about ulpropertyType: which registry string to get pdiDevInfo: the new list node Return Value: BOOL: TRUE if function succeeds, FALSE if not --*/ { ULONG ulRegDataLen = 0, ulRegDataType = 0; CONFIGRET cmret = CR_SUCCESS; PTCHAR szRegData = NULL; // // Get the length of the buffer don't bother checking return value // If RegProperty doesn't exist, we'll just move on // CM_Get_DevNode_Registry_Property(dnDevNode, ulPropertyType, NULL, NULL, &ulRegDataLen, 0); if (!ulRegDataLen || (szRegData = (PTCHAR) malloc(sizeof(TCHAR) * ulRegDataLen)) == NULL) { goto RetFALSE; } // // Now get the registry information // cmret = CM_Get_DevNode_Registry_Property( dnDevNode, ulPropertyType, &ulRegDataType, szRegData, &ulRegDataLen, 0); if (CR_SUCCESS == cmret) { if (!(CopyRegDataToDevInfoNode(pdiDevInfo, ulPropertyType, szRegData))) { goto RetFALSE; } } if (szRegData == NULL) free (szRegData); return TRUE; RetFALSE: return FALSE; } /* CopyRegistryLine */ /*++ Routine Description: (23) CopyRegDataToDevInfoNode Copies a registry string to a list node Arguments: pdiDevInfo: the new list node ulPropertyType: which registry string to copy szRegData: the data to be copied Return Value: BOOL: TRUE if function succeeds, FALSE if not --*/ BOOL CopyRegDataToDevInfoNode(IN OUT PDEV_INFO pdiDevInfo, IN ULONG ulPropertyType, IN PTCHAR szRegData) { if (pdiDevInfo == NULL) { goto RetFALSE; } switch (ulPropertyType) { case CM_DRP_DEVICEDESC: _tcsncpy(pdiDevInfo->szDescription, szRegData, MAX_PATH ); break; case CM_DRP_HARDWAREID: _tcsncpy(pdiDevInfo->szHardwareID, szRegData, MAX_PATH ); break; case CM_DRP_SERVICE: _tcsncpy(pdiDevInfo->szService, szRegData, MAX_PATH ); break; case CM_DRP_CLASS: _tcsncpy(pdiDevInfo->szClass, szRegData, MAX_PATH ); break; case CM_DRP_MFG: _tcsncpy(pdiDevInfo->szManufacturer, szRegData, MAX_PATH ); break; case CM_DRP_CONFIGFLAGS: _tcsncpy(pdiDevInfo->szConfigFlags, szRegData, MAX_PATH ); break; } return TRUE; RetFALSE: return FALSE; } /* CopyRegDataToDevInfoNode */ /*++ Routine Description: (58) InitializeInfoNode Initialized fields inside the new node Arguments: pdiDevInfo: the node szDevNodeID: used to find the dnDevNode in the future dnDevNode: the device we're storing information about Return Value: BOOL: TRUE if we should keep this node, FALSE if we should throw it away --*/ BOOL InitializeInfoNode(IN PDEV_INFO pdiDevInfo, IN PTCHAR szDevNodeID, IN DEVNODE dnDevNode) { if (pdiDevInfo) { pdiDevInfo->Next = NULL; pdiDevInfo->Prev = NULL; pdiDevInfo->szDevNodeID[0] = TEXT('\0'); pdiDevInfo->szDescription[0] = TEXT('\0'); pdiDevInfo->szHardwareID[0] = TEXT('\0'); pdiDevInfo->szService[0] = TEXT('\0'); pdiDevInfo->szClass[0] = TEXT('\0'); pdiDevInfo->szManufacturer[0] = TEXT('\0'); pdiDevInfo->szConfigFlags[0] = TEXT('\0'); pdiDevInfo->szFriendlyName[0] = TEXT('\0'); pdiDevInfo->boolSavedOrigConfiguration = FALSE; pdiDevInfo->boolDisabled = FALSE; pdiDevInfo->prddForcedResDesData = NULL; pdiDevInfo->prddAllocResDesData = NULL; pdiDevInfo->prddBasicResDesData = NULL; pdiDevInfo->prddBootResDesData = NULL; // // Store devNodeID in pdiDevInfo to get handles to devnode in future // _tcsncpy(pdiDevInfo->szDevNodeID, szDevNodeID, MAX_PATH); // Extract information from the registry about this DevNode CopyRegistryLine(dnDevNode, CM_DRP_DEVICEDESC, pdiDevInfo); CopyRegistryLine(dnDevNode, CM_DRP_HARDWAREID, pdiDevInfo); CopyRegistryLine(dnDevNode, CM_DRP_SERVICE, pdiDevInfo); CopyRegistryLine(dnDevNode, CM_DRP_CLASS, pdiDevInfo); CopyRegistryLine(dnDevNode, CM_DRP_MFG, pdiDevInfo); CopyRegistryLine(dnDevNode, CM_DRP_CONFIGFLAGS, pdiDevInfo); RecordFriendlyName(pdiDevInfo); } // // Check the friendly name to see if we want to throw this node away // if (strcmp(pdiDevInfo->szFriendlyName, "STORAGE/Volume") == 0 || strcmp(pdiDevInfo->szFriendlyName, "Unknown Device") == 0) { return FALSE; } return TRUE; } /* InitializeInfoNode */ /*++ Routine Description: (57) RecordFriendlyName Finds the best user friendly name for this device Arguments: pdiDevInfo: node containing all possible names Return Value: void --*/ void RecordFriendlyName(IN PDEV_INFO pdiDevInfo) { if (pdiDevInfo) { if (pdiDevInfo->szDescription && pdiDevInfo->szDescription[0] != TEXT('\0')) _tcsncpy(pdiDevInfo->szFriendlyName, pdiDevInfo->szDescription, MAX_PATH ); else if (pdiDevInfo->szHardwareID && pdiDevInfo->szHardwareID[0] != TEXT('\0')) _tcsncpy(pdiDevInfo->szFriendlyName, pdiDevInfo->szHardwareID, MAX_PATH); else if (pdiDevInfo->szManufacturer && pdiDevInfo->szManufacturer[0] != TEXT('\0')) _tcsncpy(pdiDevInfo->szFriendlyName, pdiDevInfo->szHardwareID, MAX_PATH); else if (pdiDevInfo->szService && pdiDevInfo->szService[0] != TEXT('\0')) _tcsncpy(pdiDevInfo->szFriendlyName, pdiDevInfo->szService, MAX_PATH); else if (pdiDevInfo->szClass && pdiDevInfo->szClass[0] != TEXT('\0')) _tcsncpy(pdiDevInfo->szFriendlyName, pdiDevInfo->szClass, MAX_PATH); else _tcscpy(pdiDevInfo->szFriendlyName, TEXT("Unknown Device")); } } /* RecordFriendlyName */ /*++ Routine Description: (24) GetResDesList Creates new resource data node and copies resource information to that node Arguments: pdiDevInfo: the list node which will contain the new resource node lcLogConf: the logical configuration information ulLogConfType: FORCED, ALLOC, BOOT, or BASIC logical configuration Return Value: BOOL: TRUE if function succeeds, FALSE if not --*/ BOOL GetResDesList(IN OUT PDEV_INFO pdiDevInfo, IN LOG_CONF lcLogConf, IN ULONG ulLogConfType) { CONFIGRET cmret, cmret2; RES_DES rdResDes = 0, rdResDesNew; RESOURCEID ridResourceID = 0; PRES_DES_DATA prddResDesData; prddResDesData = (PRES_DES_DATA)malloc(sizeof(RES_DES_DATA)); if (prddResDesData == NULL) goto c0; prddResDesData->Next = NULL; prddResDesData->Prev = NULL; prddResDesData->pmresMEMResource = NULL; prddResDesData->piresIOResource = NULL; prddResDesData->pdresDMAResource = NULL; prddResDesData->pqresIRQResource = NULL; cmret = CM_Get_Next_Res_Des(&rdResDesNew, lcLogConf, ResType_All, &ridResourceID, 0); // // Go through each resource type and copy data to new node // while (CR_SUCCESS == cmret) { rdResDes = rdResDesNew; if (ridResourceID >= ResType_Mem && ridResourceID <= ResType_IRQ) { if (!(ProcessResDesInfo(prddResDesData, rdResDes, ridResourceID))) { goto c1; } } cmret = CM_Get_Next_Res_Des(&rdResDesNew, rdResDes, ResType_All, &ridResourceID, 0); cmret2 = CM_Free_Res_Des_Handle(rdResDes); if (cmret2 != CR_SUCCESS) { //ErrorLog(24, TEXT("CM_Free_Res_Des_Handle"), cmret2, NULL); } } // Add the new node to the linked list switch (ulLogConfType) { case FORCED_LOG_CONF: if (!pdiDevInfo->prddForcedResDesData) { // // This is the first entry into the linked list // pdiDevInfo->prddForcedResDesData = prddResDesData; } else { // // Add new node to beginning of linked list // prddResDesData->Next = pdiDevInfo->prddForcedResDesData; pdiDevInfo->prddForcedResDesData->Prev = prddResDesData; pdiDevInfo->prddForcedResDesData = prddResDesData; } break; case ALLOC_LOG_CONF: if (!pdiDevInfo->prddAllocResDesData) { // // This is the first entry into the linked list // pdiDevInfo->prddAllocResDesData = prddResDesData; } else { // // Add new node to beginning of linked list // prddResDesData->Next = pdiDevInfo->prddAllocResDesData; pdiDevInfo->prddAllocResDesData->Prev = prddResDesData; pdiDevInfo->prddAllocResDesData = prddResDesData; } break; case BASIC_LOG_CONF: if (!pdiDevInfo->prddBasicResDesData) { // // This is the first entry into the linked list // pdiDevInfo->prddBasicResDesData = prddResDesData; } else { // // Add new node to beginning of linked list // prddResDesData->Next = pdiDevInfo->prddBasicResDesData; pdiDevInfo->prddBasicResDesData->Prev = prddResDesData; pdiDevInfo->prddBasicResDesData = prddResDesData; } break; case BOOT_LOG_CONF: if (!pdiDevInfo->prddBootResDesData) { // // This is the first entry into the linked list // pdiDevInfo->prddBootResDesData = prddResDesData; } else { // // Add new node to beginning of linked list // prddResDesData->Next = pdiDevInfo->prddBootResDesData; pdiDevInfo->prddBootResDesData->Prev = prddResDesData; pdiDevInfo->prddBootResDesData = prddResDesData; } break; default: goto c1; } return TRUE; c1: free(prddResDesData); c0: return FALSE; } /* GetResDestList */ /*++ Routine Description: (25) ProcessResDesInfo Gets information for one resource descriptor Arguments: prddResDesData: the new resource data node receiving the info rdResDes: the resource descriptor containing the info ridResourceID: tells the resource type (DMA, IO, MEM, IRQ, or CS) Return Value: BOOL: TRUE if function succeeds, FALSE if not --*/ BOOL ProcessResDesInfo(IN OUT PRES_DES_DATA prddResDesData, IN RES_DES rdResDes, IN RESOURCEID ridResourceID) { PVOID pvResDesDataBuffer = NULL; ULONG ulResDesDataBufferLen; CONFIGRET cmret; cmret = CM_Get_Res_Des_Data_Size(&ulResDesDataBufferLen, rdResDes, 0); if (CR_SUCCESS != cmret) { //ErrorLog(25, TEXT("CM_Get_Res_Des_Data_Size"), cmret, NULL); goto RetFALSE; } if ((pvResDesDataBuffer = malloc(sizeof(PVOID) * ulResDesDataBufferLen)) == NULL) { // Log(25, SEV2, TEXT("resDesDataBuffer malloc size of %d failed."), // ulResDesDataBufferLen); goto RetFALSE; } // // Get the data // cmret = CM_Get_Res_Des_Data(rdResDes, pvResDesDataBuffer, ulResDesDataBufferLen, 0); if (CR_SUCCESS != cmret) { //ErrorLog(25, TEXT("CM_Get_Res_Des_Data"), cmret, NULL); goto RetFALSE; } // // Copy data into ResDesData node // switch (ridResourceID) { case ResType_Mem: prddResDesData->pmresMEMResource = (PMEM_RESOURCE)pvResDesDataBuffer; break; case ResType_IO: prddResDesData->piresIOResource = (PIO_RESOURCE)pvResDesDataBuffer; break; case ResType_DMA: prddResDesData->pdresDMAResource = (PDMA_RESOURCE)pvResDesDataBuffer; break; case ResType_IRQ: prddResDesData->pqresIRQResource = (PIRQ_RESOURCE)pvResDesDataBuffer; break; default: // Log(25, SEV2, TEXT("Illegal ResourceID - %ul"), ridResourceID); goto RetFALSE; } return TRUE; RetFALSE: return FALSE; } /* ProcessResDesInfo */ /*++ Routine Description: (26) UpdateDeviceList Frees resource information for all devices and then collects the information again Arguments: none (g_pdiDevList is global head of device list) Return Value: BOOL: TRUE if function succeeds, FALSE if not --*/ BOOL UpdateDeviceList() { PDEV_INFO pdiTmpDevInfo; pdiTmpDevInfo = g_pdiDevList; // // Go through linked list and delete each node's ResDes lists // while (pdiTmpDevInfo) { if (pdiTmpDevInfo->prddForcedResDesData) { DeleteResDesDataNode(pdiTmpDevInfo->prddForcedResDesData); pdiTmpDevInfo->prddForcedResDesData = NULL; } if (pdiTmpDevInfo->prddAllocResDesData) { DeleteResDesDataNode(pdiTmpDevInfo->prddAllocResDesData); pdiTmpDevInfo->prddAllocResDesData = NULL; } if (pdiTmpDevInfo->prddBasicResDesData) { DeleteResDesDataNode(pdiTmpDevInfo->prddBasicResDesData); pdiTmpDevInfo->prddBasicResDesData = NULL; } if (pdiTmpDevInfo->prddBootResDesData) { DeleteResDesDataNode(pdiTmpDevInfo->prddBootResDesData); pdiTmpDevInfo->prddBootResDesData = NULL; } pdiTmpDevInfo = pdiTmpDevInfo->Next; } pdiTmpDevInfo = g_pdiDevList; // // Recreate the ResDesLists for each node // while (pdiTmpDevInfo) { if (!(RecreateResDesList(pdiTmpDevInfo, FORCED_LOG_CONF))) goto RetFALSE; if (!(RecreateResDesList(pdiTmpDevInfo, ALLOC_LOG_CONF))) goto RetFALSE; if (!(RecreateResDesList(pdiTmpDevInfo, BASIC_LOG_CONF))) goto RetFALSE; if (!(RecreateResDesList(pdiTmpDevInfo, BOOT_LOG_CONF))) goto RetFALSE; pdiTmpDevInfo = pdiTmpDevInfo->Next; } return TRUE; RetFALSE: return FALSE; } /* UpdateDeviceList */ /*++ Routine Description: (27) DeleteResDesDataNode Deletes a string of RES_DES_DATA structures Arguments: prddTmpResDes: the head of the linked list Return Value: void --*/ void DeleteResDesDataNode(IN PRES_DES_DATA prddTmpResDes) { PRES_DES_DATA prddNextResDes; while (prddTmpResDes) { prddNextResDes = prddTmpResDes->Next; free (prddTmpResDes); prddTmpResDes = prddNextResDes; } } /* DeleteResDesDataNode */ /*++ Routine Description: (56) CopyDataToLogConf Calls CM_Add_Res_Des to add a resDes to a lcLogConf Arguments: lcLogConf: the lcLogConf receiving the resDes ridResType: ResType_Mem, IO, DMA or IRQ pvResData: the new data ulResourceLen: size of the data Return Value: BOOL: TRUE if the CM call succeeds, FALSE if not --*/ BOOL CopyDataToLogConf(IN LOG_CONF lcLogConf, IN RESOURCEID ridResType, IN PVOID pvResData, IN ULONG ulResourceLen) { CONFIGRET cmret; RES_DES rdResDes; // // Copy the data to the logConf // cmret = CM_Add_Res_Des(&rdResDes, lcLogConf, ridResType, pvResData, ulResourceLen, 0); if (CR_SUCCESS != cmret) { goto RetFALSE; } return TRUE; RetFALSE: return FALSE; } /* CopyDataToLogConf */ /*++ Routine Description: (28) RecreateResDesList Uses CM calls to find ResDes information and creates linked list of this information inside of given DEV_INFO Arguments: pdiTmpDevInfo: the node receiving the information ulLogConfType: the LogConf type (FORCED_LOG_CONF, ALLOC_LOG_CONF, BASIC_LOG_CONF, BOOT_LOG_CONF) Return Value: BOOL: TRUE if function succeeds, FALSE if not --*/ BOOL RecreateResDesList(IN OUT PDEV_INFO pdiTmpDevInfo, IN ULONG ulLogConfType) { CONFIGRET cmret, cmret2; DEVNODE dnDevNode; LOG_CONF lcLogConf, lcLogConfNew; // // Get handle to the devnode // cmret = CM_Locate_DevNode(&dnDevNode, pdiTmpDevInfo->szDevNodeID, CM_LOCATE_DEVNODE_NORMAL); if (CR_SUCCESS != cmret) { //ErrorLog(28, TEXT("CM_Locate_DevNode"), cmret, NULL); goto RetFALSE; } // // Get logical configuration information // cmret = CM_Get_First_Log_Conf(&lcLogConfNew, dnDevNode, ulLogConfType); while (CR_SUCCESS == cmret) { lcLogConf = lcLogConfNew; if (!(GetResDesList(pdiTmpDevInfo, lcLogConf, ulLogConfType))) { goto RetFALSE; } cmret = CM_Get_Next_Log_Conf(&lcLogConfNew, lcLogConf, 0); cmret2 = CM_Free_Log_Conf_Handle(lcLogConf); if (CR_SUCCESS != cmret2) { //ErrorLog(28, TEXT("CM_Free_Log_Conf"), cmret2, NULL); } } return TRUE; RetFALSE: return FALSE; } /* RecreateResDesList */ void Cleanup() { PDEV_INFO pdiDevInfo = g_pdiDevList; PDEV_INFO pdiNextInfoNode; while (pdiDevInfo) { pdiNextInfoNode = pdiDevInfo->Next; DeleteResDesDataNode(pdiDevInfo->prddForcedResDesData); DeleteResDesDataNode(pdiDevInfo->prddAllocResDesData); DeleteResDesDataNode(pdiDevInfo->prddBasicResDesData); DeleteResDesDataNode(pdiDevInfo->prddBootResDesData); free(pdiDevInfo); pdiDevInfo = pdiNextInfoNode; } } /* Cleanup */