boards.go
1 package main 2 3 import ( 4 "fmt" 5 "io/fs" 6 "os" 7 "path/filepath" 8 "regexp" 9 "sort" 10 "strings" 11 ) 12 13 func readBoardInfo(dir NamedFS) map[string]string { 14 result := make(map[string]string) 15 c, err := fs.ReadFile(dir.FS, filepath.Join(dir.Name, "board_info.txt")) 16 if err != nil { 17 return result 18 } 19 ls := strings.Split(string(c), "\n") 20 for _, l := range ls { 21 spl := strings.SplitN(l, ":", 2) 22 if len(spl) != 2 { 23 // This shouldn't ever happen, but let's try to 24 // extract as much information from erroneous 25 // board_info files (if they exist) as possible. 26 continue 27 } 28 result[strings.TrimSpace(spl[0])] = strings.TrimSpace(spl[1]) 29 } 30 return result 31 } 32 33 func fetchBoards(dirs chan<- NamedFS) { 34 defer close(dirs) 35 ds, err := fs.Glob(cbdirFS, filepath.Join("src", "mainboard", "*", "*")) 36 if err != nil { 37 fmt.Fprintf(os.Stderr, "Could not find mainboard directories: %v\n", err) 38 return 39 } 40 for _, d := range ds { 41 if _, err := fs.ReadDir(cbdirFS, d); err != nil { 42 continue 43 } 44 dirs <- NamedFS{ 45 FS: cbdirFS, 46 Name: d, 47 } 48 } 49 } 50 51 var niceVendors = make(map[string]string) 52 53 func getNiceVendor(dir string, vendor string) (string, error) { 54 if _, exists := niceVendors[vendor]; !exists { 55 c, err := fs.ReadFile(cbdirFS, filepath.Join(dir, "..", "Kconfig.name")) 56 if err != nil { 57 return "", err 58 } 59 re, err := regexp.Compile("(?i)config VENDOR_" + vendor) 60 if err != nil { 61 return "", err 62 } 63 ls := strings.Split(string(c), "\n") 64 next := false 65 for _, l := range ls { 66 if next { 67 niceVendors[vendor] = strings.Split(l, "\"")[1] 68 break 69 } 70 if re.Match([]byte(l)) { 71 next = true 72 } 73 } 74 } 75 return niceVendors[vendor], nil 76 } 77 78 func readKconfig(dir NamedFS) (string, string, string, string, string, error) { 79 var north, south, superio, cpu, partnum string 80 c, err := fs.ReadFile(dir.FS, filepath.Join(dir.Name, "Kconfig")) 81 if err != nil { 82 return north, south, superio, cpu, partnum, err 83 } 84 ls := strings.Split(string(c), "\n") 85 partoffset := 0 86 for _, l := range ls { 87 l = strings.TrimSpace(l) 88 if len(l) < 7 { 89 continue 90 } 91 // TODO: handling of MAINBOARD_PART_NUMBER is rather broken 92 // and fragile. Doesn't help that many boards use different 93 // part numbers for different models and this code can't 94 // figure it out. 95 if strings.Contains(strings.ToLower(l), "config mainboard_part_number") { 96 partoffset = 2 97 continue 98 } 99 if partoffset > 0 { 100 partoffset-- 101 if strings.Contains(l, "default") { 102 partnum = strings.Split(l, "\"")[1] 103 continue 104 } 105 } 106 if l[0:7] != "select " { 107 continue 108 } 109 l = l[7:] 110 if len(l) > 12 && l[0:12] == "NORTHBRIDGE_" { 111 north = l[12:] 112 continue 113 } 114 if len(l) > 12 && l[0:12] == "SOUTHBRIDGE_" { 115 if strings.Contains(l, "SKIP_") || 116 strings.Contains(l, "DISABLE_") { 117 continue 118 } 119 south = l[12:] 120 continue 121 } 122 if len(l) > 8 && l[0:8] == "SUPERIO_" { 123 superio = l[8:] 124 continue 125 } 126 if len(l) > 4 && (l[0:4] == "CPU_" || l[0:4] == "SOC_") { 127 if strings.Contains(l, "AMD_AGESA_FAMILY") || 128 strings.Contains(l, "AMD_COMMON_") || 129 strings.Contains(l, "INTEL_COMMON_") || 130 strings.Contains(l, "INTEL_DISABLE_") || 131 strings.Contains(l, "INTEL_CSE_") || 132 strings.Contains(l, "CPU_MICROCODE_CBFS_NONE") { 133 continue 134 } 135 cpu = l[4:] 136 } 137 } 138 return north, south, superio, cpu, partnum, nil 139 } 140 141 type reReplace struct { 142 pattern *regexp.Regexp 143 replace string 144 } 145 146 func prettify(input string, rules *[]reReplace) string { 147 for _, rule := range *rules { 148 input = rule.pattern.ReplaceAllString(input, rule.replace) 149 } 150 return input 151 } 152 153 var northbridgeRules = []reReplace{ 154 { 155 pattern: regexp.MustCompile("AMD_AGESA_FAMILY([0-9a-fA-F]*)(.*)"), 156 replace: "AMD Family ${1}h${2} (AGESA)", 157 }, 158 { 159 pattern: regexp.MustCompile("AMD_PI_(.*)"), 160 replace: "AMD ${1} (PI)", 161 }, 162 { 163 pattern: regexp.MustCompile("INTEL_FSP_(.*)"), 164 replace: "Intel® ${1} (FSP)", 165 }, 166 { 167 pattern: regexp.MustCompile("AMD_FAMILY([0-9a-fA-F]*)"), 168 replace: "AMD Family ${1}h,", 169 }, 170 { 171 pattern: regexp.MustCompile("AMD_AMDFAM([0-9a-fA-F]*)"), 172 replace: "AMD Family ${1}h", 173 }, 174 { 175 pattern: regexp.MustCompile("_"), 176 replace: " ", 177 }, 178 { 179 pattern: regexp.MustCompile("INTEL"), 180 replace: "Intel®", 181 }, 182 } 183 184 func prettifyNorthbridge(northbridge string) string { 185 return prettify(northbridge, &northbridgeRules) 186 } 187 188 var southbridgeRules = []reReplace{ 189 { 190 pattern: regexp.MustCompile("_"), 191 replace: " ", 192 }, 193 { 194 pattern: regexp.MustCompile("INTEL"), 195 replace: "Intel®", 196 }, 197 } 198 199 func prettifySouthbridge(southbridge string) string { 200 return prettify(southbridge, &southbridgeRules) 201 } 202 203 var superIORules = []reReplace{ 204 { 205 pattern: regexp.MustCompile("_"), 206 replace: " ", 207 }, 208 { 209 pattern: regexp.MustCompile("WINBOND"), 210 replace: "Winbond™,", 211 }, 212 { 213 pattern: regexp.MustCompile("ITE"), 214 replace: "ITE™", 215 }, 216 { 217 pattern: regexp.MustCompile("SMSC"), 218 replace: "SMSC®", 219 }, 220 { 221 pattern: regexp.MustCompile("NUVOTON"), 222 replace: "Nuvoton ", 223 }, 224 } 225 226 func prettifySuperIO(superio string) string { 227 return prettify(superio, &superIORules) 228 } 229 230 type cpuMapping struct { 231 cpu string 232 socket string 233 } 234 235 var cpuMappings = map[string]cpuMapping{ 236 "ALLWINNER_A10": { 237 cpu: "Allwinner A10", 238 socket: "?", 239 }, 240 "AMD_GEODE_LX": { 241 cpu: "AMD Geode™ LX", 242 socket: "—", 243 }, 244 "AMD_SOCKET_754": { 245 cpu: "AMD Sempron™ / Athlon™ 64 / Turion™ 64", 246 socket: "Socket 754", 247 }, 248 "AMD_SOCKET_ASB2": { 249 cpu: "AMD Turion™ II Neo/Athlon™ II Neo", 250 socket: "ASB2 (BGA812)", 251 }, 252 "AMD_SOCKET_S1G1": { 253 cpu: "AMD Turion™ / X2 Sempron™", 254 socket: "Socket S1G1", 255 }, 256 "AMD_SOCKET_G34": { 257 cpu: "AMD Opteron™ Magny-Cours/Interlagos", 258 socket: "Socket G34", 259 }, 260 "AMD_SOCKET_G34_NON_AGESA": { 261 cpu: "AMD Opteron™ Magny-Cours/Interlagos", 262 socket: "Socket G34", 263 }, 264 "AMD_SOCKET_C32": { 265 cpu: "AMD Opteron™ Magny-Cours/Interlagos", 266 socket: "Socket C32", 267 }, 268 "AMD_SOCKET_C32_NON_AGESA": { 269 cpu: "AMD Opteron™ Magny-Cours/Interlagos", 270 socket: "Socket C32", 271 }, 272 "AMD_SOCKET_AM2": { 273 cpu: "?", 274 socket: "Socket AM2", 275 }, 276 "AMD_SOCKET_AM3": { 277 cpu: "AMD Athlon™ 64 / FX / X2", 278 socket: "Socket AM3", 279 }, 280 "AMD_SOCKET_AM2R2": { 281 cpu: "AMD Athlon™ 64 / X2 / FX, Sempron™", 282 socket: "Socket AM2+", 283 }, 284 "AMD_SOCKET_F": { 285 cpu: "AMD Opteron™", 286 socket: "Socket F", 287 }, 288 "AMD_SOCKET_F_1207": { 289 cpu: "AMD Opteron™", 290 socket: "Socket F 1207", 291 }, 292 "AMD_SOCKET_940": { 293 cpu: "AMD Opteron™", 294 socket: "Socket 940", 295 }, 296 "AMD_SOCKET_939": { 297 cpu: "AMD Athlon™ 64 / FX / X2", 298 socket: "Socket 939", 299 }, 300 "AMD_SC520": { 301 cpu: "AMD Élan™SC520", 302 socket: "—", 303 }, 304 "AMD_STONEYRIDGE_FP4": { 305 cpu: "AMD Stoney Ridge", 306 socket: "FP4 BGA", 307 }, 308 "ARMLTD_CORTEX_A9": { 309 cpu: "ARM Cortex A9", 310 socket: "?", 311 }, 312 "DMP_VORTEX86EX": { 313 cpu: "DMP VORTEX86EX", 314 socket: "?", 315 }, 316 "MEDIATEK_MT8173": { 317 cpu: "MediaTek MT8173", 318 socket: "—", 319 }, 320 "NVIDIA_TEGRA124": { 321 cpu: "NVIDIA Tegra 124", 322 socket: "—", 323 }, 324 "NVIDIA_TEGRA210": { 325 cpu: "NVIDIA Tegra 210", 326 socket: "—", 327 }, 328 "SAMSUNG_EXYNOS5420": { 329 cpu: "Samsung Exynos 5420", 330 socket: "?", 331 }, 332 "SAMSUNG_EXYNOS5250": { 333 cpu: "Samsung Exynos 5250", 334 socket: "?", 335 }, 336 "TI_AM335X": { 337 cpu: "TI AM335X", 338 socket: "?", 339 }, 340 "INTEL_APOLLOLAKE": { 341 cpu: "Intel® Apollo Lake", 342 socket: "—", 343 }, 344 "INTEL_BAYTRAIL": { 345 cpu: "Intel® Bay Trail", 346 socket: "—", 347 }, 348 "INTEL_BRASWELL": { 349 cpu: "Intel® Braswell", 350 socket: "—", 351 }, 352 "INTEL_BROADWELL": { 353 cpu: "Intel® Broadwell", 354 socket: "—", 355 }, 356 "INTEL_DENVERTON_NS": { 357 cpu: "Intel® Denverton-NS", 358 socket: "—", 359 }, 360 "INTEL_FSP_BROADWELL_DE": { 361 cpu: "Intel® Broadwell-DE", 362 socket: "—", 363 }, 364 "INTEL_GLK": { 365 cpu: "Intel® Gemini Lake", 366 socket: "—", 367 }, 368 "INTEL_GEMINILAKE": { 369 cpu: "Intel® Gemini Lake", 370 socket: "—", 371 }, 372 "INTEL_ICELAKE": { 373 cpu: "Intel® Ice Lake", 374 socket: "—", 375 }, 376 "INTEL_KABYLAKE": { 377 cpu: "Intel® Kaby Lake", 378 socket: "—", 379 }, 380 "INTEL_SANDYBRIDGE": { 381 cpu: "Intel® Sandy Bridge", 382 socket: "—", 383 }, 384 "INTEL_SKYLAKE": { 385 cpu: "Intel® Skylake", 386 socket: "—", 387 }, 388 "INTEL_SLOT_1": { 389 cpu: "Intel® Pentium® II/III, Celeron®", 390 socket: "Slot 1", 391 }, 392 "INTEL_SOCKET_MPGA604": { 393 cpu: "Intel® Xeon®", 394 socket: "Socket 604", 395 }, 396 "INTEL_SOCKET_M": { 397 cpu: "Intel® Core™ 2 Duo Mobile, Core™ Duo/Solo, Celeron® M", 398 socket: "Socket M (mPGA478MT)", 399 }, 400 "INTEL_SOCKET_LGA771": { 401 cpu: "Intel Xeon™ 5000 series", 402 socket: "Socket LGA771", 403 }, 404 "INTEL_SOCKET_LGA775": { 405 cpu: "Intel® Core 2, Pentium 4/D", 406 socket: "Socket LGA775", 407 }, 408 "INTEL_SOCKET_PGA370": { 409 cpu: "Intel® Pentium® III-800, Celeron®", 410 socket: "Socket PGA370", 411 }, 412 "INTEL_SOCKET_MPGA479M": { 413 cpu: "Intel® Mobile Celeron", 414 socket: "Socket 479", 415 }, 416 "INTEL_HASWELL": { 417 cpu: "Intel® 4th Gen (Haswell) Core i3/i5/i7", 418 socket: "?", 419 }, 420 "INTEL_FSP_RANGELEY": { 421 cpu: "Intel® Atom Rangeley (FSP)", 422 socket: "?", 423 }, 424 "INTEL_SOCKET_441": { 425 cpu: "Intel® Atom™ 230", 426 socket: "Socket 441", 427 }, 428 "INTEL_SOCKET_FC_PGA370": { 429 cpu: "Intel® Pentium® III, Celeron®", 430 socket: "Socket PGA370", 431 }, 432 "INTEL_EP80579": { 433 cpu: "Intel® EP80579", 434 socket: "Intel® EP80579", 435 }, 436 "INTEL_SOCKET_MFCBGA479": { 437 cpu: "Intel® Mobile Celeron", 438 socket: "Socket 479", 439 }, 440 "INTEL_WHISKEYLAKE": { 441 cpu: "Intel® Whiskey Lake", 442 socket: "—", 443 }, 444 "QC_IPQ806X": { 445 cpu: "Qualcomm IPQ806x", 446 socket: "—", 447 }, 448 "QUALCOMM_QCS405": { 449 cpu: "Qualcomm QCS405", 450 socket: "—", 451 }, 452 "ROCKCHIP_RK3288": { 453 cpu: "Rockchip RK3288", 454 socket: "—", 455 }, 456 "ROCKCHIP_RK3399": { 457 cpu: "Rockchip RK3399", 458 socket: "—", 459 }, 460 "VIA_C3": { 461 cpu: "VIA C3™", 462 socket: "?", 463 }, 464 "VIA_C7": { 465 cpu: "VIA C7™", 466 socket: "?", 467 }, 468 "VIA_NANO": { 469 cpu: "VIA NANO™", 470 socket: "?", 471 }, 472 "QEMU_X86": { 473 cpu: "QEMU x86", 474 socket: "—", 475 }, 476 } 477 478 func prettifyCPU(cpu, north string, northNice string) (string, string) { 479 if match, ok := cpuMappings[cpu]; ok { 480 return match.cpu, match.socket 481 } 482 if cpu == "" { 483 if match, ok := cpuMappings[north]; ok { 484 return match.cpu, match.socket 485 } 486 if north == "INTEL_IRONLAKE" { 487 return "Intel® 1st Gen (Westmere) Core i3/i5/i7", "?" 488 } 489 if north == "RDC_R8610" { 490 return "RDC R8610", "—" 491 } 492 if (len(north) > 10 && north[0:10] == "AMD_AGESA_") || (len(north) > 7 && north[0:7] == "AMD_PI_") { 493 return northNice, "?" 494 } 495 return north, north 496 } 497 if cpu == "INTEL_SOCKET_BGA956" { 498 if north == "INTEL_GM45" { 499 return "Intel® Core 2 Duo (Penryn)", "Socket P" 500 } 501 return "Intel® Pentium® M", "BGA956" 502 } 503 if cpu == "INTEL_SOCKET_RPGA989" || cpu == "INTEL_SOCKET_LGA1155" || cpu == "INTEL_SOCKET_RPGA988B" { 504 socket := "Socket " + cpu[13:] 505 if north == "INTEL_HASWELL" { 506 return "Intel® 4th Gen (Haswell) Core i3/i5/i7", socket 507 } 508 if north == "INTEL_IVYBRIDGE" || north == "INTEL_FSP_IVYBRIDGE" { 509 return "Intel® 3rd Gen (Ivybridge) Core i3/i5/i7", socket 510 } 511 if north == "INTEL_SANDYBRIDGE" { 512 return "Intel® 2nd Gen (Sandybridge) Core i3/i5/i7", socket 513 } 514 return north, socket 515 } 516 return cpu, cpu 517 } 518 519 func collectBoards(dirs <-chan NamedFS) { 520 for dir := range dirs { 521 path := strings.Split(dir.Name, string(filepath.Separator)) 522 vendor, board := path[2], path[3] 523 vendorNice, err := getNiceVendor(dir.Name, vendor) 524 525 if err != nil { 526 fmt.Fprintf(os.Stderr, "Could not find nice vendor name for %s: %v\n", dir.Name, err) 527 continue 528 } 529 530 bi := readBoardInfo(dir) 531 cat := Category(bi["Category"]) 532 if _, ok := data.CategoryNiceNames[cat]; !ok { 533 cat = "unclass" 534 } 535 if bi["Vendor cooperation score"] == "" { 536 bi["Vendor cooperation score"] = "—" 537 } 538 539 venboard := vendor + string(filepath.Separator) + board 540 if bi["Clone of"] != "" { 541 venboard = bi["Clone of"] 542 venboard = strings.ReplaceAll(venboard, "/", string(filepath.Separator)) 543 newpath := filepath.Join(dir.Name, "..", "..", venboard) 544 dir.Name = newpath 545 } 546 547 north, south, superio, cpu, partnum, err := readKconfig(dir) 548 if err != nil { 549 fmt.Fprintf(os.Stderr, "'%s' is not a mainboard directory: %v\n", dir.Name, err) 550 // Continue with the path because that's what the 551 // shell script did. We might want to change semantics 552 // later. 553 } 554 northbridgeNice := prettifyNorthbridge(north) 555 southbridgeNice := prettifySouthbridge(south) 556 superIONice := prettifySuperIO(superio) 557 cpuNice, socketNice := prettifyCPU(cpu, north, northbridgeNice) 558 559 boardNice := bi["Board name"] 560 if boardNice == "" { 561 boardNice = partnum 562 } 563 if boardNice == "" { 564 boardNice = strings.ReplaceAll(boardNice, "_", " ") 565 boardNice = strings.ToUpper(boardNice) 566 } 567 568 b := Board{ 569 Vendor: vendor, 570 Vendor2nd: bi["Vendor name"], 571 VendorNice: vendorNice, 572 VendorBoard: vendor + "/" + board, 573 Board: board, 574 BoardNice: boardNice, 575 BoardURL: bi["Board URL"], 576 NorthbridgeNice: northbridgeNice, 577 SouthbridgeNice: southbridgeNice, 578 SuperIONice: superIONice, 579 CPUNice: cpuNice, 580 SocketNice: socketNice, 581 ROMPackage: bi["ROM package"], 582 ROMProtocol: bi["ROM protocol"], 583 ROMSocketed: bi["ROM socketed"], 584 FlashromSupport: bi["Flashrom support"], 585 VendorCooperationScore: bi["Vendor cooperation score"], 586 VendorCooperationPage: bi["Vendor cooperation page"], 587 } 588 if b.ROMPackage == "" { 589 b.ROMPackage = "?" 590 } 591 if b.ROMProtocol == "" { 592 b.ROMProtocol = "?" 593 } 594 595 if data.BoardsByCategory[cat] == nil { 596 data.BoardsByCategory[cat] = []Board{} 597 } 598 data.BoardsByCategory[cat] = append(data.BoardsByCategory[cat], b) 599 } 600 for ci := range data.BoardsByCategory { 601 cat := data.BoardsByCategory[ci] 602 sort.Slice(data.BoardsByCategory[ci], func(i, j int) bool { 603 if cat[i].Vendor == cat[j].Vendor { 604 return cat[i].Board < cat[j].Board 605 } 606 return cat[i].Vendor < cat[j].Vendor 607 }) 608 } 609 }