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  }