| 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 |
| }) |
| } |
| } |