package inspur import ( "fmt" "regexp" "strconv" "strings" "git.mchus.pro/mchus/logpile/internal/models" ) type hgxGPUAssemblyInfo struct { Model string Part string Serial string } type hgxGPUFirmwareInfo struct { Firmware string InfoROM string } type hgxFirmwareInventoryEntry struct { ID string Version string ActiveVersion string InactiveVersion string } // Logical GPU index mapping used by HGX B200 UI ordering. // Example from real logs/UI: // GPU0->SXM5, GPU1->SXM7, GPU2->SXM6, GPU3->SXM8, GPU4->SXM2, GPU5->SXM4, GPU6->SXM3, GPU7->SXM1. var hgxLogicalToSXM = map[int]int{ 0: 5, 1: 7, 2: 6, 3: 8, 4: 2, 5: 4, 6: 3, 7: 1, } var ( reHGXGPUBlock = regexp.MustCompile(`(?s)/redfish/v1/Chassis/HGX_GPU_SXM_(\d+)/Assembly.*?"Name":\s*"GPU Board Assembly".*?"Model":\s*"([^"]+)".*?"PartNumber":\s*"([^"]+)".*?"SerialNumber":\s*"([^"]+)"`) reHGXFWBlock = regexp.MustCompile(`(?s)"Id":\s*"HGX_FW_GPU_SXM_(\d+)".*?"Version":\s*"([^"]*)"`) reHGXInfoROM = regexp.MustCompile(`(?s)"Id":\s*"HGX_InfoROM_GPU_SXM_(\d+)".*?"Version":\s*"([^"]*)"`) reIDLine = regexp.MustCompile(`"Id":\s*"([^"]+)"`) reVersion = regexp.MustCompile(`"Version":\s*"([^"]*)"`) reSlotGPU = regexp.MustCompile(`(?i)gpu\s*#?\s*(\d+)`) ) func enrichGPUsFromHGXHWInfo(content []byte, hw *models.HardwareConfig) { if hw == nil || len(hw.GPUs) == 0 || len(content) == 0 { return } bySXM := parseHGXGPUAssembly(content) if len(bySXM) == 0 { return } fwBySXM := parseHGXGPUFirmware(content) normalizeHGXGPUInventory(hw, bySXM) for i := range hw.GPUs { gpu := &hw.GPUs[i] logicalIdx, ok := extractLogicalGPUIndex(gpu.Slot) if !ok { // Keep existing info if slot index cannot be determined. continue } sxm := resolveSXMIndex(logicalIdx, bySXM) info, found := bySXM[sxm] if !found { continue } if strings.TrimSpace(gpu.SerialNumber) == "" { gpu.SerialNumber = info.Serial } if shouldReplaceGPUModel(gpu.Model) { gpu.Model = info.Model } if strings.TrimSpace(gpu.PartNumber) == "" { gpu.PartNumber = info.Part } if strings.TrimSpace(gpu.Manufacturer) == "" { gpu.Manufacturer = "NVIDIA" } if fw, ok := fwBySXM[sxm]; ok { if strings.TrimSpace(gpu.Firmware) == "" && strings.TrimSpace(fw.Firmware) != "" { gpu.Firmware = fw.Firmware } if strings.TrimSpace(gpu.VideoBIOS) == "" && strings.TrimSpace(fw.InfoROM) != "" { gpu.VideoBIOS = fw.InfoROM } } } } func appendHGXFirmwareFromHWInfo(content []byte, hw *models.HardwareConfig) { if hw == nil || len(content) == 0 { return } entries := parseHGXFirmwareInventory(content) if len(entries) == 0 { return } existing := make(map[string]bool, len(hw.Firmware)) for _, fw := range hw.Firmware { key := strings.ToLower(strings.TrimSpace(fw.DeviceName) + "|" + strings.TrimSpace(fw.Version)) existing[key] = true } appendFW := func(name, version string) { name = strings.TrimSpace(name) version = strings.TrimSpace(version) if name == "" || version == "" { return } key := strings.ToLower(name + "|" + version) if existing[key] { return } existing[key] = true hw.Firmware = append(hw.Firmware, models.FirmwareInfo{ DeviceName: name, Version: version, }) } for _, e := range entries { appendFW(e.ID, e.Version) if e.ActiveVersion != "" && e.InactiveVersion != "" && e.ActiveVersion != e.InactiveVersion { appendFW(e.ID+" Active Slot", e.ActiveVersion) appendFW(e.ID+" Inactive Slot", e.InactiveVersion) } } } func parseHGXGPUAssembly(content []byte) map[int]hgxGPUAssemblyInfo { result := make(map[int]hgxGPUAssemblyInfo) matches := reHGXGPUBlock.FindAllSubmatch(content, -1) for _, m := range matches { if len(m) != 5 { continue } sxmIdx, err := strconv.Atoi(string(m[1])) if err != nil || sxmIdx <= 0 { continue } result[sxmIdx] = hgxGPUAssemblyInfo{ Model: strings.TrimSpace(string(m[2])), Part: strings.TrimSpace(string(m[3])), Serial: strings.TrimSpace(string(m[4])), } } return result } func parseHGXGPUFirmware(content []byte) map[int]hgxGPUFirmwareInfo { result := make(map[int]hgxGPUFirmwareInfo) matchesFW := reHGXFWBlock.FindAllSubmatch(content, -1) for _, m := range matchesFW { if len(m) != 3 { continue } sxmIdx, err := strconv.Atoi(string(m[1])) if err != nil || sxmIdx <= 0 { continue } version := strings.TrimSpace(string(m[2])) if version == "" { continue } current := result[sxmIdx] if current.Firmware == "" { current.Firmware = version } result[sxmIdx] = current } matchesInfoROM := reHGXInfoROM.FindAllSubmatch(content, -1) for _, m := range matchesInfoROM { if len(m) != 3 { continue } sxmIdx, err := strconv.Atoi(string(m[1])) if err != nil || sxmIdx <= 0 { continue } version := strings.TrimSpace(string(m[2])) if version == "" { continue } current := result[sxmIdx] if current.InfoROM == "" { current.InfoROM = version } result[sxmIdx] = current } return result } func parseHGXFirmwareInventory(content []byte) []hgxFirmwareInventoryEntry { lines := strings.Split(string(content), "\n") result := make([]hgxFirmwareInventoryEntry, 0) var current *hgxFirmwareInventoryEntry section := "" flush := func() { if current == nil { return } if current.Version == "" && current.ActiveVersion == "" && current.InactiveVersion == "" { current = nil section = "" return } result = append(result, *current) current = nil section = "" } for _, line := range lines { if m := reIDLine.FindStringSubmatch(line); len(m) > 1 { flush() id := strings.TrimSpace(m[1]) if strings.HasPrefix(id, "HGX_") { current = &hgxFirmwareInventoryEntry{ID: id} } continue } if current == nil { continue } if strings.Contains(line, `"ActiveFirmwareSlot"`) { section = "active" } if strings.Contains(line, `"InactiveFirmwareSlot"`) { section = "inactive" } if m := reVersion.FindStringSubmatch(line); len(m) > 1 { version := strings.TrimSpace(m[1]) if version == "" { section = "" continue } switch section { case "active": if current.ActiveVersion == "" { current.ActiveVersion = version } case "inactive": if current.InactiveVersion == "" { current.InactiveVersion = version } default: // Keep top-level version from the last seen plain "Version" in current entry. current.Version = version } section = "" } } flush() return result } func extractLogicalGPUIndex(slot string) (int, bool) { m := reSlotGPU.FindStringSubmatch(slot) if len(m) < 2 { return 0, false } idx, err := strconv.Atoi(m[1]) if err != nil || idx < 0 { return 0, false } return idx, true } func resolveSXMIndex(logicalIdx int, bySXM map[int]hgxGPUAssemblyInfo) int { if sxm, ok := hgxLogicalToSXM[logicalIdx]; ok { if _, exists := bySXM[sxm]; exists { return sxm } } identity := logicalIdx + 1 if _, exists := bySXM[identity]; exists { return identity } return identity } func shouldReplaceGPUModel(model string) bool { trimmed := strings.TrimSpace(model) if trimmed == "" { return true } switch strings.ToLower(trimmed) { case "vga", "3d controller", "display controller", "unknown": return true default: return false } } func normalizeHGXGPUInventory(hw *models.HardwareConfig, bySXM map[int]hgxGPUAssemblyInfo) { // Keep only logical HGX GPUs (#GPU0..#GPU7) and remove BMC VGA entries. filtered := make([]models.GPU, 0, len(hw.GPUs)) present := make(map[int]bool) for _, gpu := range hw.GPUs { idx, ok := extractLogicalGPUIndex(gpu.Slot) if !ok || idx < 0 || idx > 7 { continue } present[idx] = true filtered = append(filtered, gpu) } // If some logical GPUs are missing in asset.json, add placeholders from HGX Redfish assembly. for logicalIdx := 0; logicalIdx <= 7; logicalIdx++ { if present[logicalIdx] { continue } sxm := resolveSXMIndex(logicalIdx, bySXM) info, ok := bySXM[sxm] if !ok { continue } filtered = append(filtered, models.GPU{ Slot: fmt.Sprintf("#GPU%d", logicalIdx), Model: info.Model, Manufacturer: "NVIDIA", SerialNumber: info.Serial, PartNumber: info.Part, }) } hw.GPUs = filtered }