/************************************************************************* * Copyright (c) 2019-2022, NVIDIA CORPORATION. All rights reserved. * Modifications Copyright (c) 2019-2022 Advanced Micro Devices, Inc. All rights reserved. * * See LICENSE.txt for license information ************************************************************************/ #include #include #include #include #include #include "check.h" #include "nvmlwrap.h" #include "xml.h" #include "rocm_smi_wrap.h" #include "archinfo.h" namespace sccl { namespace hardware { namespace topology { namespace topo { /**************/ /* XML Struct */ /* Functions */ /**************/ scclResult_t xmlGetAttrIndex(struct scclXmlNode* node, const char* attrName, int* index) { *index = -1; const int nAttrs = node->nAttrs; for(int a = 0; a < nAttrs; a++) { if(strncmp(node->attrs[a].key, attrName, MAX_STR_LEN) == 0) { *index = a; return scclSuccess; } } return scclSuccess; } scclResult_t xmlGetAttr(struct scclXmlNode* node, const char* attrName, const char** value) { int index; SCCLCHECK(xmlGetAttrIndex(node, attrName, &index)); *value = index == -1 ? NULL : node->attrs[index].value; return scclSuccess; } scclResult_t xmlGetAttrStr(struct scclXmlNode* node, const char* attrName, const char** value) { SCCLCHECK(xmlGetAttr(node, attrName, value)); if(*value == NULL) { WARN("Attribute %s of node %s not found", attrName, node->name); return scclInternalError; } return scclSuccess; } /** * 从XML节点属性中获取整数值 * * @param node XML节点指针 * @param attrName 属性名称 * @param value 输出参数,用于存储解析后的整数值 * @return 成功返回scclSuccess,失败返回错误码 * * @note 该函数会先获取属性字符串值,然后将其转换为整数 */ scclResult_t xmlGetAttrInt(struct scclXmlNode* node, const char* attrName, int* value) { const char* str; SCCLCHECK(xmlGetAttrStr(node, attrName, &str)); *value = strtol(str, NULL, 0); return scclSuccess; } /** * 从XML节点获取整数属性值,若属性不存在则返回默认值 * * @param node XML节点指针 * @param attrName 要获取的属性名 * @param value 输出参数,用于存储获取到的整数值 * @param defaultValue 当属性不存在时返回的默认值 * @return scclResult_t 操作结果,成功返回scclSuccess */ scclResult_t xmlGetAttrIntDefault(struct scclXmlNode* node, const char* attrName, int* value, int defaultValue) { const char* str; SCCLCHECK(xmlGetAttr(node, attrName, &str)); *value = str ? strtol(str, NULL, 0) : defaultValue; return scclSuccess; } // Only set values if not already set /** * @brief 初始化XML节点的整数属性 * * 如果属性不存在则创建并设置值,已存在则不修改 * * @param node XML节点指针 * @param attrName 属性名称 * @param value 要设置的整数值 * @return scclResult_t 返回操作结果(scclSuccess表示成功) */ scclResult_t xmlInitAttrInt(struct scclXmlNode* node, const char* attrName, const int value) { int index; SCCLCHECK(xmlGetAttrIndex(node, attrName, &index)); if(index == -1) { index = node->nAttrs++; strncpy(node->attrs[index].key, attrName, MAX_STR_LEN); snprintf(node->attrs[index].value, MAX_STR_LEN, "%d", value); } return scclSuccess; } /** * 初始化XML节点的uint64类型属性 * * @param node XML节点指针 * @param attrName 属性名称 * @param value 要设置的属性值(16进制格式) * @return 成功返回scclSuccess,失败返回错误码 * * 功能:为指定XML节点添加或更新一个uint64类型的属性,属性值将以"0x%lx"格式存储 * 注意:如果属性已存在,则直接使用新值覆盖原有值 */ scclResult_t xmlInitAttrUint64(struct scclXmlNode* node, const char* attrName, const uint64_t value) { int index; SCCLCHECK(xmlGetAttrIndex(node, attrName, &index)); if(index == -1) { index = node->nAttrs++; strncpy(node->attrs[index].key, attrName, MAX_STR_LEN); snprintf(node->attrs[index].value, MAX_STR_LEN, "0x%lx", value); } return scclSuccess; } scclResult_t xmlGetAttrFloat(struct scclXmlNode* node, const char* attrName, float* value) { const char* str; SCCLCHECK(xmlGetAttrStr(node, attrName, &str)); *value = strtof(str, NULL); return scclSuccess; } scclResult_t xmlInitAttrFloat(struct scclXmlNode* node, const char* attrName, const float value) { int index; SCCLCHECK(xmlGetAttrIndex(node, attrName, &index)); if(index == -1) { index = node->nAttrs++; strncpy(node->attrs[index].key, attrName, MAX_STR_LEN); snprintf(node->attrs[index].value, MAX_STR_LEN, "%f", value); } return scclSuccess; } scclResult_t xmlFindTag(struct scclXml* xml, const char* tagName, struct scclXmlNode** node) { *node = NULL; for(int i = 0; i < xml->maxIndex; i++) { struct scclXmlNode* n = xml->nodes + i; if(strcmp(n->name, tagName) == 0) { *node = n; return scclSuccess; } } return scclSuccess; } scclResult_t xmlFindTagKv(struct scclXml* xml, const char* tagName, struct scclXmlNode** node, const char* attrName, const char* attrValue) { *node = NULL; for(int i = 0; i < xml->maxIndex; i++) { struct scclXmlNode* n = xml->nodes + i; if(strcmp(n->name, tagName) == 0) { const char* value; SCCLCHECK(xmlGetAttr(n, attrName, &value)); if(value && strcmp(value, attrValue) == 0) { *node = n; return scclSuccess; } } } return scclSuccess; } scclResult_t xmlSetAttr(struct scclXmlNode* node, const char* attrName, const char* value) { int index; SCCLCHECK(xmlGetAttrIndex(node, attrName, &index)); if(index == -1) { index = node->nAttrs++; strncpy(node->attrs[index].key, attrName, MAX_STR_LEN); node->attrs[index].key[MAX_STR_LEN] = '\0'; } strncpy(node->attrs[index].value, value, MAX_STR_LEN); node->attrs[index].value[MAX_STR_LEN] = '\0'; return scclSuccess; } scclResult_t xmlSetAttrIfUnset(struct scclXmlNode* node, const char* attrName, const char* value) { int index; SCCLCHECK(xmlGetAttrIndex(node, attrName, &index)); if(index != -1) return scclSuccess; index = node->nAttrs++; strncpy(node->attrs[index].key, attrName, MAX_STR_LEN); node->attrs[index].key[MAX_STR_LEN] = '\0'; strncpy(node->attrs[index].value, value, MAX_STR_LEN); node->attrs[index].value[MAX_STR_LEN] = '\0'; return scclSuccess; } scclResult_t xmlSetAttrInt(struct scclXmlNode* node, const char* attrName, const int value) { int index; SCCLCHECK(xmlGetAttrIndex(node, attrName, &index)); if(index == -1) { index = node->nAttrs++; strncpy(node->attrs[index].key, attrName, MAX_STR_LEN); node->attrs[index].key[MAX_STR_LEN] = '\0'; } snprintf(node->attrs[index].value, MAX_STR_LEN, "%d", value); node->attrs[index].value[MAX_STR_LEN] = '\0'; return scclSuccess; } scclResult_t xmlSetAttrFloat(struct scclXmlNode* node, const char* attrName, const float value) { int index; SCCLCHECK(xmlGetAttrIndex(node, attrName, &index)); if(index == -1) { index = node->nAttrs++; strncpy(node->attrs[index].key, attrName, MAX_STR_LEN); node->attrs[index].key[MAX_STR_LEN] = '\0'; } snprintf(node->attrs[index].value, MAX_STR_LEN, "%g", value); node->attrs[index].value[MAX_STR_LEN] = '\0'; return scclSuccess; } scclResult_t xmlUnsetAttr(struct scclXmlNode* node, const char* attrName) { int index; SCCLCHECK(xmlGetAttrIndex(node, attrName, &index)); if(index == -1) return scclSuccess; for(int i = index + 1; i < node->nAttrs; i++) { strcpy(node->attrs[i - 1].key, node->attrs[i].key); strcpy(node->attrs[i - 1].value, node->attrs[i].value); } node->nAttrs--; return scclSuccess; } scclResult_t xmlGetSub(struct scclXmlNode* node, const char* subName, struct scclXmlNode** sub) { *sub = NULL; for(int s = 0; s < node->nSubs; s++) { if(strcmp(node->subs[s]->name, subName) == 0) { *sub = node->subs[s]; return scclSuccess; } } return scclSuccess; } scclResult_t xmlGetSubKv(struct scclXmlNode* node, const char* subName, struct scclXmlNode** sub, const char* attrName, const char* attrValue) { *sub = NULL; for(int s = 0; s < node->nSubs; s++) { struct scclXmlNode* subNode = node->subs[s]; if(strcmp(subNode->name, subName) == 0) { const char* value; SCCLCHECK(xmlGetAttr(subNode, attrName, &value)); if(value && strcmp(value, attrValue) == 0) { *sub = node->subs[s]; return scclSuccess; } } } return scclSuccess; } scclResult_t xmlGetSubKvInt(struct scclXmlNode* node, const char* subName, struct scclXmlNode** sub, const char* attrName, const int attrValue) { char strValue[10]; snprintf(strValue, 10, "%d", attrValue); SCCLCHECK(xmlGetSubKv(node, subName, sub, attrName, strValue)); return scclSuccess; } scclResult_t xmlAddNode(struct scclXml* xml, struct scclXmlNode* parent, const char* subName, struct scclXmlNode** sub) { if(xml->maxIndex == MAX_NODES) { WARN("Error : too many XML nodes (max %d)", MAX_NODES); return scclInternalError; } struct scclXmlNode* s = xml->nodes + xml->maxIndex++; s->nSubs = 0; s->nAttrs = 0; *sub = s; s->parent = parent; if(parent) parent->subs[parent->nSubs++] = s; strncpy(s->name, subName, MAX_STR_LEN); s->name[MAX_STR_LEN] = '\0'; return scclSuccess; } scclResult_t xmlRemoveNode(struct scclXmlNode* node) { node->type = NODE_TYPE_NONE; struct scclXmlNode* parent = node->parent; if(parent == NULL) return scclSuccess; int shift = 0; for(int s = 0; s < parent->nSubs; s++) { if(parent->subs[s] == node) shift = 1; else if(shift) parent->subs[s - 1] = parent->subs[s]; } parent->nSubs--; return scclSuccess; } scclResult_t kvConvertToInt(const char* str, int* value, struct kvDict* dict) { struct kvDict* d = dict; while(d->str) { if(strncmp(str, d->str, strlen(d->str)) == 0) { *value = d->value; return scclSuccess; } d++; } INFO(SCCL_LOG_GRAPH, "KV Convert to int : could not find value of '%s' in dictionary, falling back to %d", str, d->value); *value = d->value; return scclSuccess; } scclResult_t kvConvertToStr(int value, const char** str, struct kvDict* dict) { struct kvDict* d = dict; while(d->str) { if(value == d->value) { *str = d->str; return scclSuccess; } d++; } WARN("KV Convert to str : could not find value %d in dictionary", value); return scclInternalError; } namespace xml { /*******************/ /* XML File Parser */ /*******************/ scclResult_t xmlGetChar(FILE* file, char* c) { if(fread(c, 1, 1, file) == 0) { WARN("XML Parse : Unexpected EOF"); return scclInternalError; } return scclSuccess; } scclResult_t xmlGetValue(FILE* file, char* value, char* last) { char c; SCCLCHECK(xmlGetChar(file, &c)); if(c != '"' && c != '\'') { #if INT_OK int o = 0; do { value[o++] = c; SCCLCHECK(xmlGetChar(file, &c)); } while(c >= '0' && c <= '9'); value[o] = '\0'; *last = c; return scclSuccess; #else WARN("XML Parse : Expected (double) quote."); return scclInternalError; #endif } int o = 0; do { SCCLCHECK(xmlGetChar(file, &c)); value[o++] = c; } while(c != '"'); value[o - 1] = '\0'; SCCLCHECK(xmlGetChar(file, last)); return scclSuccess; } scclResult_t xmlGetToken(FILE* file, char* name, char* value, char* last) { char c; char* ptr = name; int o = 0; do { SCCLCHECK(xmlGetChar(file, &c)); if(c == '=') { ptr[o] = '\0'; if(value == NULL) { WARN("XML Parse : Unexpected value with name %s", ptr); return scclInternalError; } return xmlGetValue(file, value, last); } ptr[o] = c; if(o == MAX_STR_LEN - 1) { ptr[o] = '\0'; WARN("Error : name %s too long (max %d)", ptr, MAX_STR_LEN); return scclInternalError; } o++; } while(c != ' ' && c != '>' && c != '/' && c != '\n' && c != '\r'); ptr[o - 1] = '\0'; *last = c; return scclSuccess; } // Shift the 3-chars string by one char and append c at the end #define SHIFT_APPEND(s, c) \ do { \ s[0] = s[1]; \ s[1] = s[2]; \ s[2] = c; \ } while(0) scclResult_t xmlSkipComment(FILE* file, char* start, char next) { // Start from something neutral with \0 at the end. char end[4] = "..."; // Inject all trailing chars from previous reads. We don't need // to check for --> here because there cannot be a > in the name. for(int i = 0; i < strlen(start); i++) SHIFT_APPEND(end, start[i]); SHIFT_APPEND(end, next); // Stop when we find "-->" while(strcmp(end, "-->") != 0) { int c; if(fread(&c, 1, 1, file) != 1) { WARN("XML Parse error : unterminated comment"); return scclInternalError; } SHIFT_APPEND(end, c); } return scclSuccess; } scclResult_t xmlGetNode(FILE* file, struct scclXmlNode* node) { node->type = NODE_TYPE_NONE; char c = ' '; while(c == ' ' || c == '\n' || c == '\r') { if(fread(&c, 1, 1, file) == 0) return scclSuccess; } if(c != '<') { WARN("XML Parse error : expecting '<', got '%c'", c); return scclInternalError; } // Read XML element name SCCLCHECK(xmlGetToken(file, node->name, NULL, &c)); // Check for comments if(strncmp(node->name, "!--", 3) == 0) { SCCLCHECK(xmlSkipComment(file, node->name + 3, c)); return xmlGetNode(file, node); } // Check for closing tag if(node->name[0] == '\0' && c == '/') { node->type = NODE_TYPE_CLOSE; // Re-read the name, we got '/' in the first call SCCLCHECK(xmlGetToken(file, node->name, NULL, &c)); if(c != '>') { WARN("XML Parse error : unexpected trailing %c in closing tag %s", c, node->name); return scclInternalError; } return scclSuccess; } node->type = NODE_TYPE_OPEN; // Get Attributes int a = 0; while(c == ' ') { SCCLCHECK(xmlGetToken(file, node->attrs[a].key, node->attrs[a].value, &c)); if(a == MAX_ATTR_COUNT) { INFO(SCCL_LOG_TOPO, "XML Parse : Ignoring extra attributes (max %d)", MAX_ATTR_COUNT); // Actually we need to still consume the extra attributes so we have an extra one. } else a++; } node->nAttrs = a; if(c == '/') { node->type = NODE_TYPE_SINGLE; char str[MAX_STR_LEN]; SCCLCHECK(xmlGetToken(file, str, NULL, &c)); } if(c != '>') { WARN("XML Parse : expected >, got '%c'", c); return scclInternalError; } return scclSuccess; } typedef scclResult_t (*xmlHandlerFunc_t)(FILE*, struct scclXml*, struct scclXmlNode*); struct xmlHandler { const char* name; xmlHandlerFunc_t func; }; scclResult_t xmlLoadSub(FILE* file, struct scclXml* xml, struct scclXmlNode* head, struct xmlHandler handlers[], int nHandlers) { if(head && head->type == NODE_TYPE_SINGLE) return scclSuccess; while(1) { if(xml->maxIndex == MAX_NODES) { WARN("Error : XML parser is limited to 1024 nodes"); return scclInternalError; } struct scclXmlNode* node = xml->nodes + xml->maxIndex; memset(node, 0, sizeof(struct scclXmlNode)); SCCLCHECK(xmlGetNode(file, node)); if(node->type == NODE_TYPE_NONE) { if(head) { WARN("XML Parse : unterminated %s", head->name); return scclInternalError; } else { // All done return scclSuccess; } } if(head && node->type == NODE_TYPE_CLOSE) { if(strcmp(node->name, head->name) != 0) { WARN("XML Mismatch : %s / %s", head->name, node->name); return scclInternalError; } return scclSuccess; } int found = 0; for(int h = 0; h < nHandlers; h++) { if(strcmp(node->name, handlers[h].name) == 0) { if(head) head->subs[head->nSubs++] = node; node->parent = head; node->nSubs = 0; xml->maxIndex++; SCCLCHECK(handlers[h].func(file, xml, node)); found = 1; break; } } if(!found) { if(nHandlers) INFO(SCCL_LOG_TOPO, "Ignoring element %s", node->name); SCCLCHECK(xmlLoadSub(file, xml, node, NULL, 0)); } } } /**************/ /* XML Writer */ /**************/ scclResult_t scclTopoDumpXmlRec(int indent, FILE* file, struct scclXmlNode* node) { for(int i = 0; i < indent; i++) fprintf(file, " "); fprintf(file, "<%s", node->name); for(int a = 0; a < node->nAttrs; a++) { fprintf(file, " %s=\"%s\"", node->attrs[a].key, node->attrs[a].value); } if(node->nSubs == 0) { fprintf(file, "/>\n"); } else { fprintf(file, ">\n"); for(int s = 0; s < node->nSubs; s++) { SCCLCHECK(scclTopoDumpXmlRec(indent + 2, file, node->subs[s])); } for(int i = 0; i < indent; i++) fprintf(file, " "); fprintf(file, "\n", node->name); } return scclSuccess; } /****************************************/ /* Parser rules for our specific format */ /****************************************/ scclResult_t scclTopoXmlLoadNvlink(FILE* file, struct scclXml* xml, struct scclXmlNode* head) { SCCLCHECK(xmlLoadSub(file, xml, head, NULL, 0)); return scclSuccess; } scclResult_t scclTopoXmlLoadGpu(FILE* file, struct scclXml* xml, struct scclXmlNode* head) { struct xmlHandler handlers[] = {{"xgmi", scclTopoXmlLoadNvlink}}; SCCLCHECK(xmlLoadSub(file, xml, head, handlers, 1)); return scclSuccess; } scclResult_t scclTopoXmlLoadNet(FILE* file, struct scclXml* xml, struct scclXmlNode* head) { SCCLCHECK(xmlLoadSub(file, xml, head, NULL, 0)); return scclSuccess; } scclResult_t scclTopoXmlLoadNic(FILE* file, struct scclXml* xml, struct scclXmlNode* head) { struct xmlHandler handlers[] = {{"net", scclTopoXmlLoadNet}}; SCCLCHECK(xmlLoadSub(file, xml, head, handlers, 1)); return scclSuccess; } scclResult_t scclTopoXmlLoadPci(FILE* file, struct scclXml* xml, struct scclXmlNode* head) { struct xmlHandler handlers[] = {{"pci", scclTopoXmlLoadPci}, {"gpu", scclTopoXmlLoadGpu}, {"nic", scclTopoXmlLoadNic}}; SCCLCHECK(xmlLoadSub(file, xml, head, handlers, 3)); return scclSuccess; } scclResult_t scclTopoXmlLoadCpu(FILE* file, struct scclXml* xml, struct scclXmlNode* head) { struct xmlHandler handlers[] = {{"pci", scclTopoXmlLoadPci}, {"nic", scclTopoXmlLoadNic}}; SCCLCHECK(xmlLoadSub(file, xml, head, handlers, 2)); return scclSuccess; } scclResult_t scclTopoXmlLoadSystem(FILE* file, struct scclXml* xml, struct scclXmlNode* head) { int version; SCCLCHECK(xmlGetAttrInt(head, "version", &version)); if(version != SCCL_TOPO_XML_VERSION) { WARN("XML Topology has wrong version %d, %d needed", version, SCCL_TOPO_XML_VERSION); return scclInvalidUsage; } const char* name; SCCLCHECK(xmlGetAttr(head, "name", &name)); if(name != NULL) INFO(SCCL_LOG_TOPO, "Loading topology %s", name); else INFO(SCCL_LOG_TOPO, "Loading unnamed topology"); struct xmlHandler handlers[] = {{"cpu", scclTopoXmlLoadCpu}}; SCCLCHECK(xmlLoadSub(file, xml, head, handlers, 1)); return scclSuccess; } /**********************/ /* XML creation */ /* from autodetection */ /**********************/ #define BUSID_SIZE (sizeof("0000:00:00.0")) #define BUSID_REDUCED_SIZE (sizeof("0000:00")) static void memcpylower(char* dst, const char* src, const size_t size) { for(int i = 0; i < size; i++) dst[i] = tolower(src[i]); return; } static scclResult_t getPciPath(const char* busId, char** path) { char busPath[] = "/sys/class/pci_bus/0000:00/../../0000:00:00.0"; memcpylower(busPath + sizeof("/sys/class/pci_bus/") - 1, busId, BUSID_REDUCED_SIZE - 1); memcpylower(busPath + sizeof("/sys/class/pci_bus/0000:00/../../") - 1, busId, BUSID_SIZE - 1); *path = realpath(busPath, NULL); if(*path == NULL) { WARN("Could not find real path of %s", busPath); return scclSystemError; } return scclSuccess; } scclResult_t scclTopoSetAttrFromSys(struct scclXmlNode* pciNode, const char* path, const char* fileName, const char* attrName) { char strValue[MAX_STR_LEN]; SCCLCHECK(scclTopoGetStrFromSys(path, fileName, strValue)); if(strValue[0] != '\0') { SCCLCHECK(xmlSetAttr(pciNode, attrName, strValue)); } INFO(SCCL_LOG_TOPO, "Read from sys %s/%s -> %s=%s", path, fileName, attrName, strValue); return scclSuccess; } scclResult_t scclTopoGetXmlFromCpu(struct scclXmlNode* cpuNode, struct scclXml* xml) { int index; SCCLCHECK(xmlGetAttrIndex(cpuNode, "affinity", &index)); if(index == -1) { const char* numaId; SCCLCHECK(xmlGetAttr(cpuNode, "numaid", &numaId)); if(numaId == NULL) { WARN("GetXmlFromCpu : could not find CPU numa ID."); return scclInternalError; } // Set affinity char cpumaskPath[] = "/sys/devices/system/node/node0000"; sprintf(cpumaskPath, "/sys/devices/system/node/node%s", numaId); SCCLCHECK(scclTopoSetAttrFromSys(cpuNode, cpumaskPath, "cpumap", "affinity")); } SCCLCHECK(xmlGetAttrIndex(cpuNode, "arch", &index)); if(index == -1) { // Fill CPU type / vendor / model #if defined(__PPC__) SCCLCHECK(xmlSetAttr(cpuNode, "arch", "ppc64")); #elif defined(__aarch64__) SCCLCHECK(xmlSetAttr(cpuNode, "arch", "arm64")); #elif defined(__x86_64__) SCCLCHECK(xmlSetAttr(cpuNode, "arch", "x86_64")); #endif } #if defined(__x86_64__) SCCLCHECK(xmlGetAttrIndex(cpuNode, "vendor", &index)); if(index == -1) { union { struct { // CPUID 0 String register order uint32_t ebx; uint32_t edx; uint32_t ecx; }; char vendor[12]; } cpuid0; asm volatile("cpuid" : "=b"(cpuid0.ebx), "=c"(cpuid0.ecx), "=d"(cpuid0.edx) : "a"(0) : "memory"); char vendor[13]; strncpy(vendor, cpuid0.vendor, 12); vendor[12] = '\0'; SCCLCHECK(xmlSetAttr(cpuNode, "vendor", vendor)); } SCCLCHECK(xmlGetAttrIndex(cpuNode, "familyid", &index)); if(index == -1) { union { struct { unsigned steppingId : 4; unsigned modelId : 4; unsigned familyId : 4; unsigned processorType : 2; unsigned resv0 : 2; unsigned extModelId : 4; unsigned extFamilyId : 8; unsigned resv1 : 4; }; uint32_t val; } cpuid1; asm volatile("cpuid" : "=a"(cpuid1.val) : "a"(1) : "memory"); int familyId = cpuid1.familyId + (cpuid1.extFamilyId << 4); int modelId = cpuid1.modelId + (cpuid1.extModelId << 4); SCCLCHECK(xmlSetAttrInt(cpuNode, "familyid", familyId)); SCCLCHECK(xmlSetAttrInt(cpuNode, "modelid", modelId)); } #endif return scclSuccess; } scclResult_t scclTopoGetPciNode(struct scclXml* xml, const char* busId, struct scclXmlNode** pciNode) { SCCLCHECK(xmlFindTagKv(xml, "pci", pciNode, "busid", busId)); if(*pciNode == NULL) { SCCLCHECK(xmlAddNode(xml, NULL, "pci", pciNode)); SCCLCHECK(xmlSetAttr(*pciNode, "busid", busId)); } return scclSuccess; } // Check whether a string is in BDF format or not. // BDF (Bus-Device-Function) is "BBBB:BB:DD.F" where B, D and F are hex digits. // There can be trailing chars. int isHex(char c) { return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); } int checkBDFFormat(char* bdf) { if(bdf[4] != ':' || bdf[7] != ':' || bdf[10] != '.') return 0; if(isHex(bdf[0]) == 0 || isHex(bdf[1] == 0) || isHex(bdf[2] == 0) || isHex(bdf[3] == 0) || isHex(bdf[5] == 0) || isHex(bdf[6] == 0) || isHex(bdf[8] == 0) || isHex(bdf[9] == 0) || isHex(bdf[11] == 0)) return 0; return 1; } scclResult_t scclTopoGetXmlFromSys(struct scclXmlNode* pciNode, struct scclXml* xml) { // Fill info, then parent const char* busId; SCCLCHECK(xmlGetAttr(pciNode, "busid", &busId)); char* path = NULL; getPciPath(busId, &path); if(path) { SCCLCHECK(scclTopoSetAttrFromSys(pciNode, path, "class", "class")); } int index; SCCLCHECK(xmlGetAttrIndex(pciNode, "vendor", &index)); if(index == -1) { if(path) scclTopoSetAttrFromSys(pciNode, path, "vendor", "vendor"); } SCCLCHECK(xmlGetAttrIndex(pciNode, "device", &index)); if(index == -1) { if(path) scclTopoSetAttrFromSys(pciNode, path, "device", "device"); } SCCLCHECK(xmlGetAttrIndex(pciNode, "subsystem_vendor", &index)); if(index == -1) { if(path) scclTopoSetAttrFromSys(pciNode, path, "subsystem_vendor", "subsystem_vendor"); } SCCLCHECK(xmlGetAttrIndex(pciNode, "subsystem_device", &index)); if(index == -1) { if(path) scclTopoSetAttrFromSys(pciNode, path, "subsystem_device", "subsystem_device"); } SCCLCHECK(xmlGetAttrIndex(pciNode, "link_speed", &index)); if(index == -1) { if(path) { char deviceSpeedStr[MAX_STR_LEN]; float deviceSpeed; SCCLCHECK(scclTopoGetStrFromSys(path, "max_link_speed", deviceSpeedStr)); sscanf(deviceSpeedStr, "%f GT/s", &deviceSpeed); char portSpeedStr[MAX_STR_LEN]; float portSpeed; SCCLCHECK(scclTopoGetStrFromSys(path, "../max_link_speed", portSpeedStr)); if(portSpeedStr[0]) sscanf(portSpeedStr, "%f GT/s", &portSpeed); else portSpeed = deviceSpeed; SCCLCHECK(xmlSetAttr(pciNode, "link_speed", portSpeed < deviceSpeed ? portSpeedStr : deviceSpeedStr)); } else { SCCLCHECK(xmlSetAttr(pciNode, "link_speed", "")); } } SCCLCHECK(xmlGetAttrIndex(pciNode, "link_width", &index)); if(index == -1) { if(path) { char strValue[MAX_STR_LEN]; SCCLCHECK(scclTopoGetStrFromSys(path, "max_link_width", strValue)); int deviceWidth = strtol(strValue, NULL, 0); SCCLCHECK(scclTopoGetStrFromSys(path, "../max_link_width", strValue)); int portWidth; if(strValue[0]) portWidth = strtol(strValue, NULL, 0); else portWidth = deviceWidth; SCCLCHECK(xmlSetAttrInt(pciNode, "link_width", std::min(deviceWidth, portWidth))); } else { SCCLCHECK(xmlSetAttr(pciNode, "link_width", "")); } } struct scclXmlNode* parent = pciNode->parent; if(parent == NULL) { if(path) { // Save that for later in case next step is a CPU char numaIdStr[MAX_STR_LEN]; SCCLCHECK(scclTopoGetStrFromSys(path, "numa_node", numaIdStr)); // Workaround kernel bug for now if(strcmp(numaIdStr, "-1") == 0) strcpy(numaIdStr, "0"); // Go up one level in the PCI tree. Rewind two "/" and follow the upper PCI // switch, or stop if we reach a CPU root complex. int slashCount = 0; int parentOffset; for(parentOffset = strlen(path) - 1; parentOffset > 0; parentOffset--) { if(path[parentOffset] == '/') { slashCount++; path[parentOffset] = '\0'; int start = parentOffset - 1; while(start > 0 && path[start] != '/') start--; // Check whether the parent path looks like "BBBB:BB:DD.F" or not. if(checkBDFFormat(path + start + 1) == 0) { // This a CPU root complex. Create a CPU tag and stop there. struct scclXmlNode* topNode; SCCLCHECK(xmlFindTag(xml, "system", &topNode)); SCCLCHECK(xmlGetSubKv(topNode, "cpu", &parent, "numaid", numaIdStr)); if(parent == NULL) { SCCLCHECK(xmlAddNode(xml, topNode, "cpu", &parent)); SCCLCHECK(xmlSetAttr(parent, "numaid", numaIdStr)); } } else if(slashCount == 2) { // Continue on the upper PCI switch for(int i = strlen(path) - 1; i > 0; i--) { if(path[i] == '/') { SCCLCHECK(xmlFindTagKv(xml, "pci", &parent, "busid", path + i + 1)); if(parent == NULL) { SCCLCHECK(xmlAddNode(xml, NULL, "pci", &parent)); SCCLCHECK(xmlSetAttr(parent, "busid", path + i + 1)); } break; } } } } if(parent) break; } } else { // No information on /sys, attach GPU to unknown CPU SCCLCHECK(xmlFindTagKv(xml, "cpu", &parent, "numaid", "-1")); if(parent == NULL) { struct scclXmlNode* topNode; SCCLCHECK(xmlFindTag(xml, "system", &topNode)); SCCLCHECK(xmlAddNode(xml, topNode, "cpu", &parent)); SCCLCHECK(xmlSetAttr(parent, "numaid", "-1")); SCCLCHECK(scclTopoGetXmlFromCpu(parent, xml)); } } pciNode->parent = parent; parent->subs[parent->nSubs++] = pciNode; } if(strcmp(parent->name, "pci") == 0) { SCCLCHECK(scclTopoGetXmlFromSys(parent, xml)); } else if(strcmp(parent->name, "cpu") == 0) { SCCLCHECK(scclTopoGetXmlFromCpu(parent, xml)); } free(path); return scclSuccess; } scclResult_t scclTopoGetXmlFromGpu(struct scclXmlNode* pciNode, uint32_t rocmDev, struct scclXml* xml, struct scclXmlNode** gpuNodeRet) { struct scclXmlNode* gpuNode = NULL; SCCLCHECK(xmlGetSub(pciNode, "gpu", &gpuNode)); if(gpuNode == NULL) SCCLCHECK(xmlAddNode(xml, pciNode, "gpu", &gpuNode)); int index = -1; int dev = -1; SCCLCHECK(xmlGetAttrIndex(gpuNode, "dev", &index)); if(index == -1) { if(rocmDev == -1) { const char* busId; SCCLCHECK(xmlGetAttr(pciNode, "busid", &busId)); if(busId == NULL || hipDeviceGetByPCIBusId(&dev, busId) != hipSuccess) dev = -1; } else { dev = rocmDev; } SCCLCHECK(xmlSetAttrInt(gpuNode, "dev", dev)); } SCCLCHECK(xmlGetAttrInt(gpuNode, "dev", &dev)); if(dev == -1) { *gpuNodeRet = NULL; return scclSuccess; } SCCLCHECK(xmlGetAttrIndex(gpuNode, "sm", &index)); if(index == -1) { int hipMajor, hipMinor; hipDeviceProp_t devProp; HIPCHECK(hipGetDeviceProperties(&devProp, 0)); hipMajor = devProp.major; hipMinor = devProp.minor; SCCLCHECK(xmlSetAttrInt(gpuNode, "sm", hipMajor * 10 + hipMinor)); } int sm; SCCLCHECK(xmlGetAttrInt(gpuNode, "sm", &sm)); const char* gcn; const char* gcnArchName; SCCLCHECK(xmlGetAttrIndex(gpuNode, "gcn", &index)); if(index == -1) { hipDeviceProp_t devProp; HIPCHECK(hipGetDeviceProperties(&devProp, 0)); // extract only the releveant info from the gcnArchName attribute // e.g.: convert "gfx908:sramecc+:xnack-" to "gfx908" char gcnArchNameSubstr[6]; GcnArchNameFormat(devProp.gcnArchName, gcnArchNameSubstr); gcn = gcnArchNameSubstr; SCCLCHECK(xmlSetAttr(gpuNode, "gcn", gcn)); } SCCLCHECK(xmlGetAttr(gpuNode, "gcn", &gcn)); convertGcnArchToGcnArchName(gcn, &gcnArchName); SCCLCHECK(xmlSetAttr(gpuNode, "gcn", gcnArchName)); scclHipDeviceArch_t arch; SCCLCHECK(xmlGetAttrIndex(gpuNode, "arch", &index)); if(index == -1) { hipDeviceProp_t devProp; HIPCHECK(hipGetDeviceProperties(&devProp, 0)); memcpy(&arch.arch, &devProp.arch, sizeof(hipDeviceArch_t)); SCCLCHECK(xmlSetAttrInt(gpuNode, "arch", arch.value)); } SCCLCHECK(xmlGetAttrInt(gpuNode, "arch", &arch.value)); struct scclXmlNode* nvlNode = NULL; SCCLCHECK(xmlGetSub(gpuNode, "nvlink", &nvlNode)); if(nvlNode == NULL) { const char* busId; SCCLCHECK(xmlGetAttr(pciNode, "busid", &busId)); uint32_t deviceCnt; SCCLCHECK(rocm_smi_getNumDevice(&deviceCnt)); for(int i = 0; i < deviceCnt; i++) { if(i != dev) { RSMI_IO_LINK_TYPE rsmi_type; int hops, count; if(rocm_smi_getLinkInfo(dev, i, &rsmi_type, &hops, &count) == scclSuccess) { if(rsmi_type >= RSMI_IOLINK_TYPE_XGMI && hops >= 1) { char busIdStr[] = "00000000:00:00.0"; SCCLCHECK(rocm_smi_getDevicePciBusIdString(i, busIdStr, sizeof(busIdStr))); char lowerId[NVML_DEVICE_PCI_BUS_ID_BUFFER_SIZE]; for(int c = 0; c < NVML_DEVICE_PCI_BUS_ID_BUFFER_SIZE; c++) { lowerId[c] = tolower(busIdStr[c]); if(busIdStr[c] == 0) break; } SCCLCHECK(xmlGetSubKv(gpuNode, "xgmi", &nvlNode, "target", lowerId)); if(nvlNode == NULL) { SCCLCHECK(xmlAddNode(xml, gpuNode, "xgmi", &nvlNode)); SCCLCHECK(xmlSetAttr(nvlNode, "target", lowerId)); SCCLCHECK(xmlSetAttrInt(nvlNode, "count", count)); } } } } } } // Fill target classes for(int s = 0; s < gpuNode->nSubs; s++) { struct scclXmlNode* sub = gpuNode->subs[s]; if(strcmp(sub->name, "xgmi") != 0) continue; int index; SCCLCHECK(xmlGetAttrIndex(sub, "tclass", &index)); if(index == -1) { const char* busId; SCCLCHECK(xmlGetAttr(sub, "target", &busId)); char* path; getPciPath(busId, &path); if(path == NULL || strcmp(busId, "fffffff:ffff:ff") == 0) { // Remote NVLink device is not visible inside this VM. Assume NVSwitch. SCCLCHECK(xmlSetAttr(sub, "tclass", "0x068000")); } else { SCCLCHECK(scclTopoSetAttrFromSys(sub, path, "class", "tclass")); free(path); } } } *gpuNodeRet = gpuNode; return scclSuccess; } // Returns the subsystem name of a path, i.e. the end of the path // where sysPath/subsystem points to. scclResult_t scclTopoGetSubsystem(const char* sysPath, char* subSys) { char subSysPath[PATH_MAX]; sprintf(subSysPath, "%s/subsystem", sysPath); char* path = realpath(subSysPath, NULL); if(path == NULL) { subSys[0] = '\0'; } else { int offset; for(offset = strlen(path); offset > 0 && path[offset] != '/'; offset--) ; strcpy(subSys, path + offset + 1); free(path); } return scclSuccess; } scclResult_t scclTopoTrimXmlRec(struct scclXmlNode* node) { const char* str; SCCLCHECK(xmlGetAttr(node, "keep", &str)); if(str && strcmp(str, "1") == 0) { SCCLCHECK(xmlUnsetAttr(node, "keep")); } else { // Copy nSubs and subs as they could change as we trim recursively. struct scclXmlNode* subs[MAX_SUBS]; int nSubs = node->nSubs; memcpy(subs, node->subs, node->nSubs * sizeof(struct scclXmlNode*)); for(int s = 0; s < nSubs; s++) { SCCLCHECK(scclTopoTrimXmlRec(subs[s])); } if(node->nSubs == 0) SCCLCHECK(xmlRemoveNode(node)); } return scclSuccess; } /**************************************************/ /* Parser rules for the user-defined graph search */ /**************************************************/ scclResult_t scclTopoXmlGraphLoadGpu(FILE* file, struct scclXml* xml, struct scclXmlNode* head) { SCCLCHECK(xmlLoadSub(file, xml, head, NULL, 0)); return scclSuccess; } scclResult_t scclTopoXmlGraphLoadNet(FILE* file, struct scclXml* xml, struct scclXmlNode* head) { SCCLCHECK(xmlLoadSub(file, xml, head, NULL, 0)); return scclSuccess; } scclResult_t scclTopoXmlGraphLoadChannel(FILE* file, struct scclXml* xml, struct scclXmlNode* head) { struct xmlHandler handlers[] = {{"net", scclTopoXmlGraphLoadNet}, {"gpu", scclTopoXmlGraphLoadGpu}}; SCCLCHECK(xmlLoadSub(file, xml, head, handlers, 2)); return scclSuccess; } scclResult_t scclTopoXmlGraphLoadGraph(FILE* file, struct scclXml* xml, struct scclXmlNode* head) { struct xmlHandler handlers[] = {{"channel", scclTopoXmlGraphLoadChannel}}; SCCLCHECK(xmlLoadSub(file, xml, head, handlers, 1)); return scclSuccess; } scclResult_t scclTopoXmlGraphLoadGraphs(FILE* file, struct scclXml* xmlGraph, struct scclXmlNode* head) { int version; SCCLCHECK(xmlGetAttrInt(head, "version", &version)); if(version != SCCL_GRAPH_XML_VERSION) { WARN("XML Graph has wrong version %d, %d needed", version, SCCL_GRAPH_XML_VERSION); return scclInvalidUsage; } const char* name; SCCLCHECK(xmlGetAttr(head, "name", &name)); if(name != NULL) INFO(SCCL_LOG_TOPO, "Loading graphs for topology %s", name); else INFO(SCCL_LOG_TOPO, "Loading graphs"); struct xmlHandler handlers[] = {{"graph", scclTopoXmlGraphLoadGraph}}; SCCLCHECK(xmlLoadSub(file, xmlGraph, head, handlers, 1)); return scclSuccess; } } // namespace xml scclResult_t scclTopoGetXmlFromFile(const char* xmlTopoFile, struct scclXml* xml, int warn) { FILE* file = fopen(xmlTopoFile, "r"); if(file == NULL) { if(warn) { WARN("Could not open XML topology file %s : %s", xmlTopoFile, strerror(errno)); } return scclSuccess; } INFO(SCCL_LOG_TOPO, "Loading topology file %s", xmlTopoFile); struct xml::xmlHandler handlers[] = {{"system", xml::scclTopoXmlLoadSystem}}; xml->maxIndex = 0; SCCLCHECK(xml::xmlLoadSub(file, xml, NULL, handlers, 1)); fclose(file); return scclSuccess; } scclResult_t scclTopoDumpXmlToFile(const char* xmlTopoFile, struct scclXml* xml) { FILE* file = fopen(xmlTopoFile, "w"); if(file == NULL) { WARN("Unable to open %s, not dumping topology.", xmlTopoFile); return scclSuccess; } SCCLCHECK(xml::scclTopoDumpXmlRec(0, file, xml->nodes)); fclose(file); return scclSuccess; } scclResult_t scclTopoFillGpu(struct scclXml* xml, const char* busId, struct scclXmlNode** gpuNode) { struct scclXmlNode* node; SCCLCHECK(xml::scclTopoGetPciNode(xml, busId, &node)); SCCLCHECK(xmlSetAttrIfUnset(node, "class", "0x03")); SCCLCHECK(xml::scclTopoGetXmlFromSys(node, xml)); uint32_t devIndex; static int rocmsmiInit = 0; if(rocmsmiInit == 0) { rocmsmiInit = (rocm_smi_init() != scclSuccess) ? 2 : 1; } if(rocmsmiInit == 1) { if(rocm_smi_getDeviceIndexByPciBusId(busId, &devIndex) != scclSuccess) devIndex = -1; } SCCLCHECK(xml::scclTopoGetXmlFromGpu(node, devIndex, xml, gpuNode)); return scclSuccess; } scclResult_t scclTopoFillNet(struct scclXml* xml, const char* pciPath, const char* netName, struct scclXmlNode** netNode) { SCCLCHECK(xmlFindTagKv(xml, "net", netNode, "name", netName)); if(*netNode != NULL) return scclSuccess; const char* pciSysPath = pciPath; if(pciSysPath) { char subSystem[PATH_MAX]; SCCLCHECK(xml::scclTopoGetSubsystem(pciSysPath, subSystem)); // This is not a PCI device (virtual, usb, ...). if(strcmp(subSystem, "pci") != 0) { INFO(SCCL_LOG_TOPO, "Topology detection: network path %s is not a PCI device (%s). Attaching to first CPU", pciSysPath, subSystem); pciSysPath = NULL; } } struct scclXmlNode* parent = NULL; if(pciSysPath) { int offset; for(offset = strlen(pciSysPath) - 1; pciSysPath[offset] != '/'; offset--) ; char busId[NVML_DEVICE_PCI_BUS_ID_BUFFER_SIZE]; strcpy(busId, pciSysPath + offset + 1); SCCLCHECK(xml::scclTopoGetPciNode(xml, busId, &parent)); SCCLCHECK(xmlSetAttrIfUnset(parent, "class", "0x02")); SCCLCHECK(xml::scclTopoGetXmlFromSys(parent, xml)); } else { // Virtual NIC, no PCI device, attach to first CPU SCCLCHECK(xmlFindTag(xml, "cpu", &parent)); } struct scclXmlNode* nicNode = NULL; SCCLCHECK(xmlGetSub(parent, "nic", &nicNode)); if(nicNode == NULL) { SCCLCHECK(xmlAddNode(xml, parent, "nic", &nicNode)); } // We know that this net does not exist yet (we searched for it at the // beginning of this function), so we can add it. SCCLCHECK(xmlAddNode(xml, nicNode, "net", netNode)); SCCLCHECK(xmlSetAttr(*netNode, "name", netName)); return scclSuccess; } scclResult_t scclTopoGetXmlGraphFromFile(const char* xmlGraphFile, struct scclXml* xml) { FILE* file = fopen(xmlGraphFile, "r"); if(file == NULL) { WARN("Could not open XML graph file %s : %s", xmlGraphFile, strerror(errno)); return scclSystemError; } struct xml::xmlHandler handlers[] = {{"graphs", xml::scclTopoXmlGraphLoadGraphs}}; xml->maxIndex = 0; SCCLCHECK(xml::xmlLoadSub(file, xml, NULL, handlers, 1)); fclose(file); return scclSuccess; } scclResult_t scclTopoTrimXml(struct scclXml* xml) { SCCLCHECK(xml::scclTopoTrimXmlRec(xml->nodes)); return scclSuccess; } scclResult_t scclTopoGetStrFromSys(const char* path, const char* fileName, char* strValue) { char filePath[PATH_MAX]; sprintf(filePath, "%s/%s", path, fileName); int offset = 0; FILE* file; if((file = fopen(filePath, "r")) != NULL) { while(feof(file) == 0 && ferror(file) == 0 && offset < MAX_STR_LEN) { int len = fread(strValue + offset, 1, MAX_STR_LEN - offset, file); offset += len; } fclose(file); } if(offset == 0) { strValue[0] = '\0'; INFO(SCCL_LOG_TOPO, "Topology detection : could not read %s, ignoring", filePath); } else { strValue[offset - 1] = '\0'; } return scclSuccess; } } // namespace topo } // namespace topology } // namespace hardware } // namespace sccl