package main import ( "fmt" "io/fs" "os" "path/filepath" "regexp" "sort" "strings" ) func readBoardInfo(dir NamedFS) map[string]string { result := make(map[string]string) c, err := fs.ReadFile(dir.FS, filepath.Join(dir.Name, "board_info.txt")) if err != nil { return result } ls := strings.Split(string(c), "\n") for _, l := range ls { spl := strings.SplitN(l, ":", 2) if len(spl) != 2 { // This shouldn't ever happen, but let's try to // extract as much information from erroneous // board_info files (if they exist) as possible. continue } result[strings.TrimSpace(spl[0])] = strings.TrimSpace(spl[1]) } return result } func fetchBoards(dirs chan<- NamedFS) { defer close(dirs) ds, err := fs.Glob(cbdirFS, filepath.Join("src", "mainboard", "*", "*")) if err != nil { fmt.Fprintf(os.Stderr, "Could not find mainboard directories: %v\n", err) return } for _, d := range ds { if _, err := fs.ReadDir(cbdirFS, d); err != nil { continue } dirs <- NamedFS{ FS: cbdirFS, Name: d, } } } var niceVendors = make(map[string]string) func getNiceVendor(dir string, vendor string) (string, error) { if _, exists := niceVendors[vendor]; !exists { c, err := fs.ReadFile(cbdirFS, filepath.Join(dir, "..", "Kconfig.name")) if err != nil { return "", err } re, err := regexp.Compile("(?i)config VENDOR_" + vendor) if err != nil { return "", err } ls := strings.Split(string(c), "\n") next := false for _, l := range ls { if next { niceVendors[vendor] = strings.Split(l, "\"")[1] break } if re.Match([]byte(l)) { next = true } } } return niceVendors[vendor], nil } func readKconfig(dir NamedFS) (string, string, string, string, string, error) { var north, south, superio, cpu, partnum string c, err := fs.ReadFile(dir.FS, filepath.Join(dir.Name, "Kconfig")) if err != nil { return north, south, superio, cpu, partnum, err } ls := strings.Split(string(c), "\n") partoffset := 0 for _, l := range ls { l = strings.TrimSpace(l) if len(l) < 7 { continue } // TODO: handling of MAINBOARD_PART_NUMBER is rather broken // and fragile. Doesn't help that many boards use different // part numbers for different models and this code can't // figure it out. if strings.Contains(strings.ToLower(l), "config mainboard_part_number") { partoffset = 2 continue } if partoffset > 0 { partoffset-- if strings.Contains(l, "default") { partnum = strings.Split(l, "\"")[1] continue } } if l[0:7] != "select " { continue } l = l[7:] if len(l) > 12 && l[0:12] == "NORTHBRIDGE_" { north = l[12:] continue } if len(l) > 12 && l[0:12] == "SOUTHBRIDGE_" { if strings.Contains(l, "SKIP_") || strings.Contains(l, "DISABLE_") { continue } south = l[12:] continue } if len(l) > 8 && l[0:8] == "SUPERIO_" { superio = l[8:] continue } if len(l) > 4 && (l[0:4] == "CPU_" || l[0:4] == "SOC_") { if strings.Contains(l, "AMD_AGESA_FAMILY") || strings.Contains(l, "AMD_COMMON_") || strings.Contains(l, "INTEL_COMMON_") || strings.Contains(l, "INTEL_DISABLE_") || strings.Contains(l, "INTEL_CSE_") || strings.Contains(l, "CPU_MICROCODE_CBFS_NONE") { continue } cpu = l[4:] } } return north, south, superio, cpu, partnum, nil } type reReplace struct { pattern *regexp.Regexp replace string } func prettify(input string, rules *[]reReplace) string { for _, rule := range *rules { input = rule.pattern.ReplaceAllString(input, rule.replace) } return input } var northbridgeRules = []reReplace{ { pattern: regexp.MustCompile("AMD_AGESA_FAMILY([0-9a-fA-F]*)(.*)"), replace: "AMD Family ${1}h${2} (AGESA)", }, { pattern: regexp.MustCompile("AMD_PI_(.*)"), replace: "AMD ${1} (PI)", }, { pattern: regexp.MustCompile("INTEL_FSP_(.*)"), replace: "Intel® ${1} (FSP)", }, { pattern: regexp.MustCompile("AMD_FAMILY([0-9a-fA-F]*)"), replace: "AMD Family ${1}h,", }, { pattern: regexp.MustCompile("AMD_AMDFAM([0-9a-fA-F]*)"), replace: "AMD Family ${1}h", }, { pattern: regexp.MustCompile("_"), replace: " ", }, { pattern: regexp.MustCompile("INTEL"), replace: "Intel®", }, } func prettifyNorthbridge(northbridge string) string { return prettify(northbridge, &northbridgeRules) } var southbridgeRules = []reReplace{ { pattern: regexp.MustCompile("_"), replace: " ", }, { pattern: regexp.MustCompile("INTEL"), replace: "Intel®", }, } func prettifySouthbridge(southbridge string) string { return prettify(southbridge, &southbridgeRules) } var superIORules = []reReplace{ { pattern: regexp.MustCompile("_"), replace: " ", }, { pattern: regexp.MustCompile("WINBOND"), replace: "Winbond™,", }, { pattern: regexp.MustCompile("ITE"), replace: "ITE™", }, { pattern: regexp.MustCompile("SMSC"), replace: "SMSC®", }, { pattern: regexp.MustCompile("NUVOTON"), replace: "Nuvoton ", }, } func prettifySuperIO(superio string) string { return prettify(superio, &superIORules) } type cpuMapping struct { cpu string socket string } var cpuMappings = map[string]cpuMapping{ "ALLWINNER_A10": { cpu: "Allwinner A10", socket: "?", }, "AMD_GEODE_LX": { cpu: "AMD Geode™ LX", socket: "—", }, "AMD_SOCKET_754": { cpu: "AMD Sempron™ / Athlon™ 64 / Turion™ 64", socket: "Socket 754", }, "AMD_SOCKET_ASB2": { cpu: "AMD Turion™ II Neo/Athlon™ II Neo", socket: "ASB2 (BGA812)", }, "AMD_SOCKET_S1G1": { cpu: "AMD Turion™ / X2 Sempron™", socket: "Socket S1G1", }, "AMD_SOCKET_G34": { cpu: "AMD Opteron™ Magny-Cours/Interlagos", socket: "Socket G34", }, "AMD_SOCKET_G34_NON_AGESA": { cpu: "AMD Opteron™ Magny-Cours/Interlagos", socket: "Socket G34", }, "AMD_SOCKET_C32": { cpu: "AMD Opteron™ Magny-Cours/Interlagos", socket: "Socket C32", }, "AMD_SOCKET_C32_NON_AGESA": { cpu: "AMD Opteron™ Magny-Cours/Interlagos", socket: "Socket C32", }, "AMD_SOCKET_AM2": { cpu: "?", socket: "Socket AM2", }, "AMD_SOCKET_AM3": { cpu: "AMD Athlon™ 64 / FX / X2", socket: "Socket AM3", }, "AMD_SOCKET_AM2R2": { cpu: "AMD Athlon™ 64 / X2 / FX, Sempron™", socket: "Socket AM2+", }, "AMD_SOCKET_F": { cpu: "AMD Opteron™", socket: "Socket F", }, "AMD_SOCKET_F_1207": { cpu: "AMD Opteron™", socket: "Socket F 1207", }, "AMD_SOCKET_940": { cpu: "AMD Opteron™", socket: "Socket 940", }, "AMD_SOCKET_939": { cpu: "AMD Athlon™ 64 / FX / X2", socket: "Socket 939", }, "AMD_SC520": { cpu: "AMD Élan™SC520", socket: "—", }, "AMD_STONEYRIDGE_FP4": { cpu: "AMD Stoney Ridge", socket: "FP4 BGA", }, "ARMLTD_CORTEX_A9": { cpu: "ARM Cortex A9", socket: "?", }, "DMP_VORTEX86EX": { cpu: "DMP VORTEX86EX", socket: "?", }, "MEDIATEK_MT8173": { cpu: "MediaTek MT8173", socket: "—", }, "NVIDIA_TEGRA124": { cpu: "NVIDIA Tegra 124", socket: "—", }, "NVIDIA_TEGRA210": { cpu: "NVIDIA Tegra 210", socket: "—", }, "SAMSUNG_EXYNOS5420": { cpu: "Samsung Exynos 5420", socket: "?", }, "SAMSUNG_EXYNOS5250": { cpu: "Samsung Exynos 5250", socket: "?", }, "TI_AM335X": { cpu: "TI AM335X", socket: "?", }, "INTEL_APOLLOLAKE": { cpu: "Intel® Apollo Lake", socket: "—", }, "INTEL_BAYTRAIL": { cpu: "Intel® Bay Trail", socket: "—", }, "INTEL_BRASWELL": { cpu: "Intel® Braswell", socket: "—", }, "INTEL_BROADWELL": { cpu: "Intel® Broadwell", socket: "—", }, "INTEL_DENVERTON_NS": { cpu: "Intel® Denverton-NS", socket: "—", }, "INTEL_FSP_BROADWELL_DE": { cpu: "Intel® Broadwell-DE", socket: "—", }, "INTEL_GLK": { cpu: "Intel® Gemini Lake", socket: "—", }, "INTEL_GEMINILAKE": { cpu: "Intel® Gemini Lake", socket: "—", }, "INTEL_ICELAKE": { cpu: "Intel® Ice Lake", socket: "—", }, "INTEL_KABYLAKE": { cpu: "Intel® Kaby Lake", socket: "—", }, "INTEL_SANDYBRIDGE": { cpu: "Intel® Sandy Bridge", socket: "—", }, "INTEL_SKYLAKE": { cpu: "Intel® Skylake", socket: "—", }, "INTEL_SLOT_1": { cpu: "Intel® Pentium® II/III, Celeron®", socket: "Slot 1", }, "INTEL_SOCKET_MPGA604": { cpu: "Intel® Xeon®", socket: "Socket 604", }, "INTEL_SOCKET_M": { cpu: "Intel® Core™ 2 Duo Mobile, Core™ Duo/Solo, Celeron® M", socket: "Socket M (mPGA478MT)", }, "INTEL_SOCKET_LGA771": { cpu: "Intel Xeon™ 5000 series", socket: "Socket LGA771", }, "INTEL_SOCKET_LGA775": { cpu: "Intel® Core 2, Pentium 4/D", socket: "Socket LGA775", }, "INTEL_SOCKET_PGA370": { cpu: "Intel® Pentium® III-800, Celeron®", socket: "Socket PGA370", }, "INTEL_SOCKET_MPGA479M": { cpu: "Intel® Mobile Celeron", socket: "Socket 479", }, "INTEL_HASWELL": { cpu: "Intel® 4th Gen (Haswell) Core i3/i5/i7", socket: "?", }, "INTEL_FSP_RANGELEY": { cpu: "Intel® Atom Rangeley (FSP)", socket: "?", }, "INTEL_SOCKET_441": { cpu: "Intel® Atom™ 230", socket: "Socket 441", }, "INTEL_SOCKET_FC_PGA370": { cpu: "Intel® Pentium® III, Celeron®", socket: "Socket PGA370", }, "INTEL_EP80579": { cpu: "Intel® EP80579", socket: "Intel® EP80579", }, "INTEL_SOCKET_MFCBGA479": { cpu: "Intel® Mobile Celeron", socket: "Socket 479", }, "INTEL_WHISKEYLAKE": { cpu: "Intel® Whiskey Lake", socket: "—", }, "QC_IPQ806X": { cpu: "Qualcomm IPQ806x", socket: "—", }, "QUALCOMM_QCS405": { cpu: "Qualcomm QCS405", socket: "—", }, "ROCKCHIP_RK3288": { cpu: "Rockchip RK3288", socket: "—", }, "ROCKCHIP_RK3399": { cpu: "Rockchip RK3399", socket: "—", }, "VIA_C3": { cpu: "VIA C3™", socket: "?", }, "VIA_C7": { cpu: "VIA C7™", socket: "?", }, "VIA_NANO": { cpu: "VIA NANO™", socket: "?", }, "QEMU_X86": { cpu: "QEMU x86", socket: "—", }, } func prettifyCPU(cpu, north string, northNice string) (string, string) { if match, ok := cpuMappings[cpu]; ok { return match.cpu, match.socket } if cpu == "" { if match, ok := cpuMappings[north]; ok { return match.cpu, match.socket } if north == "INTEL_IRONLAKE" { return "Intel® 1st Gen (Westmere) Core i3/i5/i7", "?" } if north == "RDC_R8610" { return "RDC R8610", "—" } if (len(north) > 10 && north[0:10] == "AMD_AGESA_") || (len(north) > 7 && north[0:7] == "AMD_PI_") { return northNice, "?" } return north, north } if cpu == "INTEL_SOCKET_BGA956" { if north == "INTEL_GM45" { return "Intel® Core 2 Duo (Penryn)", "Socket P" } return "Intel® Pentium® M", "BGA956" } if cpu == "INTEL_SOCKET_RPGA989" || cpu == "INTEL_SOCKET_LGA1155" || cpu == "INTEL_SOCKET_RPGA988B" { socket := "Socket " + cpu[13:] if north == "INTEL_HASWELL" { return "Intel® 4th Gen (Haswell) Core i3/i5/i7", socket } if north == "INTEL_IVYBRIDGE" || north == "INTEL_FSP_IVYBRIDGE" { return "Intel® 3rd Gen (Ivybridge) Core i3/i5/i7", socket } if north == "INTEL_SANDYBRIDGE" { return "Intel® 2nd Gen (Sandybridge) Core i3/i5/i7", socket } return north, socket } return cpu, cpu } func collectBoards(dirs <-chan NamedFS) { for dir := range dirs { path := strings.Split(dir.Name, string(filepath.Separator)) vendor, board := path[2], path[3] vendorNice, err := getNiceVendor(dir.Name, vendor) if err != nil { fmt.Fprintf(os.Stderr, "Could not find nice vendor name for %s: %v\n", dir.Name, err) continue } bi := readBoardInfo(dir) cat := Category(bi["Category"]) if _, ok := data.CategoryNiceNames[cat]; !ok { cat = "unclass" } if bi["Vendor cooperation score"] == "" { bi["Vendor cooperation score"] = "—" } venboard := vendor + string(filepath.Separator) + board if bi["Clone of"] != "" { venboard = bi["Clone of"] venboard = strings.ReplaceAll(venboard, "/", string(filepath.Separator)) newpath := filepath.Join(dir.Name, "..", "..", venboard) dir.Name = newpath } north, south, superio, cpu, partnum, err := readKconfig(dir) if err != nil { fmt.Fprintf(os.Stderr, "'%s' is not a mainboard directory: %v\n", dir.Name, err) // Continue with the path because that's what the // shell script did. We might want to change semantics // later. } northbridgeNice := prettifyNorthbridge(north) southbridgeNice := prettifySouthbridge(south) superIONice := prettifySuperIO(superio) cpuNice, socketNice := prettifyCPU(cpu, north, northbridgeNice) boardNice := bi["Board name"] if boardNice == "" { boardNice = partnum } if boardNice == "" { boardNice = strings.ReplaceAll(boardNice, "_", " ") boardNice = strings.ToUpper(boardNice) } b := Board{ Vendor: vendor, Vendor2nd: bi["Vendor name"], VendorNice: vendorNice, VendorBoard: vendor + "/" + board, Board: board, BoardNice: boardNice, BoardURL: bi["Board URL"], NorthbridgeNice: northbridgeNice, SouthbridgeNice: southbridgeNice, SuperIONice: superIONice, CPUNice: cpuNice, SocketNice: socketNice, ROMPackage: bi["ROM package"], ROMProtocol: bi["ROM protocol"], ROMSocketed: bi["ROM socketed"], FlashromSupport: bi["Flashrom support"], VendorCooperationScore: bi["Vendor cooperation score"], VendorCooperationPage: bi["Vendor cooperation page"], } if b.ROMPackage == "" { b.ROMPackage = "?" } if b.ROMProtocol == "" { b.ROMProtocol = "?" } if data.BoardsByCategory[cat] == nil { data.BoardsByCategory[cat] = []Board{} } data.BoardsByCategory[cat] = append(data.BoardsByCategory[cat], b) } for ci := range data.BoardsByCategory { cat := data.BoardsByCategory[ci] sort.Slice(data.BoardsByCategory[ci], func(i, j int) bool { if cat[i].Vendor == cat[j].Vendor { return cat[i].Board < cat[j].Board } return cat[i].Vendor < cat[j].Vendor }) } }