summaryrefslogtreecommitdiff
path: root/util
diff options
context:
space:
mode:
Diffstat (limited to 'util')
-rw-r--r--util/docker/coreboot.org-status/board-status.html/boards.go601
-rw-r--r--util/docker/coreboot.org-status/board-status.html/go.mod23
-rw-r--r--util/docker/coreboot.org-status/board-status.html/go.sum84
-rw-r--r--util/docker/coreboot.org-status/board-status.html/logs.go165
-rw-r--r--util/docker/coreboot.org-status/board-status.html/status-to-html.go92
-rw-r--r--util/docker/coreboot.org-status/board-status.html/templates/board-status.html133
-rw-r--r--util/docker/coreboot.org-status/board-status.html/types.go60
7 files changed, 1158 insertions, 0 deletions
diff --git a/util/docker/coreboot.org-status/board-status.html/boards.go b/util/docker/coreboot.org-status/board-status.html/boards.go
new file mode 100644
index 0000000000..dc68cb6d46
--- /dev/null
+++ b/util/docker/coreboot.org-status/board-status.html/boards.go
@@ -0,0 +1,601 @@
+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"] = "—"
+ }
+
+ 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
+ })
+ }
+}
diff --git a/util/docker/coreboot.org-status/board-status.html/go.mod b/util/docker/coreboot.org-status/board-status.html/go.mod
new file mode 100644
index 0000000000..7fcf434cd6
--- /dev/null
+++ b/util/docker/coreboot.org-status/board-status.html/go.mod
@@ -0,0 +1,23 @@
+module review.coreboot.org/coreboot.git/util/docker/coreboot.org-status/board-status.html
+
+go 1.17
+
+require (
+ github.com/Microsoft/go-winio v0.4.16 // indirect
+ github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 // indirect
+ github.com/acomagu/bufpipe v1.0.3 // indirect
+ github.com/emirpasic/gods v1.12.0 // indirect
+ github.com/go-git/gcfg v1.5.0 // indirect
+ github.com/go-git/go-billy/v5 v5.3.1 // indirect
+ github.com/go-git/go-git/v5 v5.4.2 // indirect
+ github.com/imdario/mergo v0.3.12 // indirect
+ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
+ github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
+ github.com/mitchellh/go-homedir v1.1.0 // indirect
+ github.com/sergi/go-diff v1.1.0 // indirect
+ github.com/xanzy/ssh-agent v0.3.0 // indirect
+ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect
+ golang.org/x/net v0.0.0-20210326060303-6b1517762897 // indirect
+ golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79 // indirect
+ gopkg.in/warnings.v0 v0.1.2 // indirect
+)
diff --git a/util/docker/coreboot.org-status/board-status.html/go.sum b/util/docker/coreboot.org-status/board-status.html/go.sum
new file mode 100644
index 0000000000..2ea7b18d45
--- /dev/null
+++ b/util/docker/coreboot.org-status/board-status.html/go.sum
@@ -0,0 +1,84 @@
+github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
+github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk=
+github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
+github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ=
+github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
+github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
+github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
+github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
+github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
+github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
+github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
+github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
+github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
+github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
+github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
+github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34=
+github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
+github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0=
+github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4=
+github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
+github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
+github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
+github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck=
+github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
+github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
+github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
+github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
+github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
+golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg=
+golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210326060303-6b1517762897 h1:KrsHThm5nFk34YtATK1LsThyGhGbGe1olrte/HInHvs=
+golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79 h1:RX8C8PRZc2hTIod4ds8ij+/4RQX3AqhYj3uOHmyaz4E=
+golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
+gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/util/docker/coreboot.org-status/board-status.html/logs.go b/util/docker/coreboot.org-status/board-status.html/logs.go
new file mode 100644
index 0000000000..21e91f2d9a
--- /dev/null
+++ b/util/docker/coreboot.org-status/board-status.html/logs.go
@@ -0,0 +1,165 @@
+package main
+
+import (
+ "fmt"
+ "io/fs"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "sort"
+ "strings"
+ "time"
+)
+
+// Color returns a HTML color code between green and yellow based on the
+// number of days that passed since ds.
+func (ds DateString) Color() string {
+ date, _ := time.Parse("2006-01-02T15:04:05Z", string(ds))
+ days := int(time.Since(date).Hours() / 24)
+ if days > 255 {
+ days = 255
+ }
+ return fmt.Sprintf("#%02xff00", days)
+}
+
+func fetchLogs(dirs chan<- NamedFS) {
+ err := fs.WalkDir(bsdirFS, ".", func(path string, d fs.DirEntry, err error) error {
+ if err != nil {
+ return err
+ }
+ if path[0] == '.' {
+ return nil
+ }
+ if d.IsDir() && len(strings.Split(path, string(filepath.Separator))) == 4 {
+ dirs <- NamedFS{
+ FS: bsdirFS,
+ Name: path,
+ }
+ }
+ return nil
+ })
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Reading logs failed: %v\n", err)
+ }
+ close(dirs)
+}
+
+func collectLogs(dirs <-chan NamedFS) {
+ data.Logs = make(map[Timeframe][]Log)
+ data.VendorBoardDate = make(map[string]DateString)
+ data.VendorBoardReferenced = make(map[string]bool)
+ timeframes := make(map[Timeframe]bool)
+ gitcache := make(map[string]string)
+ for dir := range dirs {
+ upstream := ""
+ revB, err := fs.ReadFile(dir.FS, filepath.Join(dir.Name, "revision.txt"))
+ if err != nil {
+ continue
+ }
+ rev := string(revB)
+ skipDir := false
+ for _, line := range strings.Split(rev, "\n") {
+ item := strings.SplitN(line, ":", 2)
+ if len(item) != 2 {
+ // This is an error, but let's try to extract
+ // as much value out of revision.txt files as
+ // possible, even if some lines are erroneous.
+ continue
+ }
+ if item[0] == "Upstream revision" {
+ upstream = strings.TrimSpace(item[1])
+ // tried using go-git, but its resolver
+ // couldn't expand short hashes despite the
+ // docs claiming that it can.
+ if val, ok := gitcache[upstream]; ok {
+ upstream = val
+ } else {
+ res, err := exec.Command("/usr/bin/git", "-C", cbdir, "log", "-n1", "--format=%H", upstream).Output()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "revision %s not found \n", upstream)
+ skipDir = true
+ break
+ }
+ gitcache[upstream] = strings.TrimSpace(string(res))
+ upstream = gitcache[upstream]
+ }
+ }
+ }
+ if skipDir {
+ continue
+ }
+
+ rawFiles, err := fs.Glob(dir.FS, filepath.Join(dir.Name, "*"))
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Could not fetch log data, skipping: %v\n", err)
+ continue
+ }
+
+ pieces := strings.Split(dir.Name, string(filepath.Separator))
+ if len(pieces) < 4 {
+ fmt.Fprintf(os.Stderr, "log directory %s is malformed, skipping\n", dir.Name)
+ continue
+ }
+ vendorBoard := strings.Join(pieces[:2], "/")
+ // TODO: these need to become "second to last" and "last" item
+ // but only after compatibility to the current system has been ensured.
+ dateTimePath := pieces[3]
+ dateTime, err := time.Parse(time.RFC3339, strings.ReplaceAll(dateTimePath, "_", ":"))
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Could not parse timestamp from %s: %v\n", dir.Name, err)
+ continue
+ }
+ dateTimeNormal := dateTime.UTC().Format("2006-01-02T15:04:05Z")
+ dateTimeHuman := dateTime.UTC().Format(time.UnixDate)
+ tfYear, tfWeek := dateTime.ISOWeek()
+ timeframe := Timeframe(fmt.Sprintf("%dW%02d", tfYear, tfWeek))
+
+ if !timeframes[timeframe] {
+ timeframes[timeframe] = true
+ data.Timeframes = append(data.Timeframes, timeframe)
+ data.Logs[timeframe] = []Log{}
+ }
+
+ files := []Path{}
+ l := len(dir.Name) + 1
+ for _, file := range rawFiles {
+ if file[l:] == "revision.txt" {
+ continue
+ }
+ files = append(files, Path{
+ Path: dir.Name + "/",
+ Basename: file[l:],
+ })
+ }
+
+ data.Logs[timeframe] = append(data.Logs[timeframe], Log{
+ VendorBoard: vendorBoard,
+ Time: dateTimeNormal,
+ TimeReadable: dateTimeHuman,
+ Upstream: upstream,
+ Files: files,
+ })
+ }
+ sort.Slice(data.Timeframes, func(i, j int) bool {
+ // reverse sort
+ return data.Timeframes[i] > data.Timeframes[j]
+ })
+ for bi := range data.Logs {
+ bucket := data.Logs[bi]
+ sort.Slice(data.Logs[bi], func(i, j int) bool {
+ if bucket[i].Time == bucket[j].Time {
+ return (bucket[i].VendorBoard > bucket[j].VendorBoard)
+ }
+ return (bucket[i].Time > bucket[j].Time)
+ })
+ }
+ for _, ts := range data.Timeframes {
+ for li, l := range data.Logs[ts] {
+ if _, match := data.VendorBoardDate[l.VendorBoard]; match {
+ continue
+ }
+ data.VendorBoardDate[l.VendorBoard] = DateString(l.Time)
+ data.Logs[ts][li].Reference = true
+ }
+ }
+}
diff --git a/util/docker/coreboot.org-status/board-status.html/status-to-html.go b/util/docker/coreboot.org-status/board-status.html/status-to-html.go
new file mode 100644
index 0000000000..ae341faac8
--- /dev/null
+++ b/util/docker/coreboot.org-status/board-status.html/status-to-html.go
@@ -0,0 +1,92 @@
+package main
+
+import (
+ "embed"
+ "errors"
+ "flag"
+ "fmt"
+ "html/template"
+ "io/fs"
+ "os"
+ "path/filepath"
+)
+
+//go:embed templates
+var templates embed.FS
+
+var data = TemplateData{
+ Categories: []Category{
+ "laptop",
+ "server",
+ "desktop",
+ "half",
+ "mini",
+ "settop",
+ "eval",
+ "sbc",
+ "emulation",
+ "misc",
+ "unclass",
+ },
+ CategoryNiceNames: map[Category]string{
+ "desktop": "Desktops / Workstations",
+ "server": "Servers",
+ "laptop": "Laptops",
+ "half": "Embedded / PC/104 / Half-size boards",
+ "mini": "Mini-ITX / Micro-ITX / Nano-ITX",
+ "settop": "Set-top-boxes / Thin clients",
+ "eval": "Devel/Eval Boards",
+ "sbc": "Single-Board computer",
+ "emulation": "Emulation",
+ "misc": "Miscellaneous",
+ "unclass": "Unclassified",
+ },
+ BoardsByCategory: map[Category][]Board{},
+}
+
+var (
+ cbdirFS fs.FS
+ cbdir string
+ bsdirFS fs.FS
+)
+
+func main() {
+ var cbDir, bsDir string
+ flag.StringVar(&cbDir, "coreboot-dir", filepath.Join("..", "coreboot.git"), "coreboot.git checkout")
+ flag.StringVar(&bsDir, "board-status-dir", filepath.Join("..", "board-status.git"), "board-status.git checkout")
+ flag.Parse()
+
+ tpls, err := template.ParseFS(templates, filepath.Join("templates", "*"))
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Parsing templates failed: %v\n", err)
+ os.Exit(1)
+ }
+
+ if _, err := os.Stat(cbDir); errors.Is(err, os.ErrNotExist) {
+ fmt.Fprintf(os.Stderr, "coreboot root %s does not exist\n", cbDir)
+ os.Exit(1)
+ }
+
+ if _, err := os.Stat(bsDir); errors.Is(err, os.ErrNotExist) {
+ fmt.Fprintf(os.Stderr, "board-status dir %s does not exist\n", bsDir)
+ os.Exit(1)
+ }
+
+ cbdirFS = os.DirFS(cbDir)
+ cbdir = cbDir
+ bsdirFS = os.DirFS(bsDir)
+
+ dirs := make(chan NamedFS)
+ go fetchLogs(dirs)
+ collectLogs(dirs)
+
+ dirs = make(chan NamedFS)
+ go fetchBoards(dirs)
+ collectBoards(dirs)
+
+ err = tpls.ExecuteTemplate(os.Stdout, "board-status.html", data)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Executing template failed: %v\n", err)
+ os.Exit(1)
+ }
+}
diff --git a/util/docker/coreboot.org-status/board-status.html/templates/board-status.html b/util/docker/coreboot.org-status/board-status.html/templates/board-status.html
new file mode 100644
index 0000000000..93fcc45101
--- /dev/null
+++ b/util/docker/coreboot.org-status/board-status.html/templates/board-status.html
@@ -0,0 +1,133 @@
+{{$data := . -}}
+{{- define "colorcode" -}}
+{{- if eq . "n" }}<td style="background:red">N</td>{{else
+ if eq . "y" }}<td style="background:lime">Y</td>{{else
+ if eq . "" }}<td>?</td>{{else
+ }}<td>{{.}}</td>{{end -}}
+{{end -}}
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>status report for coreboot boards</title>
+</head>
+<body>
+<h1>Mainboards supported by coreboot</h1>
+
+<p>This page shows two representations of the same data:</p>
+
+<p>First a list of all mainboards supported by coreboot (current within
+one hour) ordered by category. For each mainboard the table shows the
+latest user-contributed report of a successful boot on the device.</p>
+
+<p>After that, the page provides a time-ordered list of these contributed
+reports, with the newest report first.</p>
+
+<p>Boards without such reports may boot or there may be some maintenance
+required. The reports contain the coreboot configuration and precise commit
+id, so it is possible to reproduce the build.</p>
+
+<p>We encourage developers and users to contribute reports so we know which
+devices are well-tested. We have
+<a href='https://review.coreboot.org/plugins/gitiles/coreboot/+/refs/heads/master/util/board_status/'>a tool in the coreboot repository</a>
+to make contributing easy. The data resides in the
+<a href='https://review.coreboot.org/plugins/gitiles/board-status/'>board status repository</a>.
+Contributing requires an account on review.coreboot.org. After
+logging into the site with your preferred OpenID or GitHub/Google
+credentials, you can get a user name and password for git pushes on <a
+href="https://review.coreboot.org/settings/#HTTPCredentials">gerrit's
+settings screen</a>.</p>
+
+<p>Sometimes the same board is sold under different names, we've tried to
+list all known names but some names might be missing.</p>
+
+<p>If the board is not found in the coreboot's source code, there might
+be some form of support that is not ready yet for inclusion in coreboot,
+usually people willing to send their patches to coreboot goes through
+<a href='https://review.coreboot.org'>gerrit</a>, so looking there could find some
+code for boards that are not yet merged.</p>
+
+<h1>Vendor trees</h1>
+<p>Some vendors have their own coreboot trees/fork, for instance:
+<ul>
+ <li><a href='https://chromium.googlesource.com/chromiumos/third_party/coreboot/'>chrome/chromium's tree</a>
+</ul>
+</p>
+<h1>Motherboards supported in coreboot</h1>
+
+<table border="0" style="font-size: smaller">
+<tr bgcolor="#6699ff">
+<td>Vendor</td>
+<td>Mainboard</td>
+<td>Latest known good</td>
+<td>Northbridge</td>
+<td>Southbridge</td>
+<td>Super&nbsp;I/O</td>
+<td>CPU</td>
+<td>Socket</td>
+<td><span title="ROM chip package">ROM&nbsp;<sup>1</sup></span></td>
+<td><span title="ROM chip protocol">P&nbsp;<sup>2</sup></span></td>
+<td><span title="ROM chip socketed?">S&nbsp;<sup>3</sup></span></td>
+<td><span title="Board supported by flashrom?">F&nbsp;<sup>4</sup></span></td>
+<td><span title="Vendor Cooperation Score">VCS<sup>5</sup></span></td>
+</tr>
+{{range $category := .Categories -}}
+<tr bgcolor="#6699ff">
+<td colspan="13"><h4>{{index $data.CategoryNiceNames $category}}</h4></td>
+</tr>
+
+{{$color := "#eeeeee" -}}
+{{$oldVendor := "" -}}
+{{range $b := index $data.BoardsByCategory $category -}}
+{{if ne $oldVendor $b.VendorNice}}{{$oldVendor = $b.VendorNice -}}
+{{if eq $color "#dddddd"}}{{$color = "#eeeeee"}}{{else}}{{$color = "#dddddd"}}{{end -}}
+{{end -}}
+<tr bgcolor="{{$color}}">
+<td>{{if $b.BoardURL}}<a href='{{$b.BoardURL}}'>{{$b.VendorNice}}</a>{{else}}{{$b.VendorNice}}{{end}}
+{{if $b.Vendor2nd}} ({{$b.Vendor2nd}})
+{{end -}}
+</td><td><a href='https://www.coreboot.org/Board:{{$b.Vendor}}/{{$b.Board}}'>{{$b.BoardNice}}</a></td>
+{{if eq "" (index $data.VendorBoardDate $b.VendorBoard) -}}
+<td style="background:red">Unknown</td>
+{{- else -}}
+{{- $date := index $data.VendorBoardDate $b.VendorBoard -}}
+<td style="background:{{ $date.Color }}"><a href='#{{ $b.VendorBoard }}'>{{ $date }}</a></td>
+{{- end}}
+<td>{{$b.NorthbridgeNice}}</td>
+<td>{{$b.SouthbridgeNice}}</td>
+<td>{{$b.SuperIONice}}</td>
+<td>{{$b.CPUNice}}</td>
+<td>{{$b.SocketNice}}</td>
+<td>{{$b.ROMPackage}}</td>
+<td>{{$b.ROMProtocol}}</td>
+{{template "colorcode" $b.ROMSocketed}}
+{{template "colorcode" $b.FlashromSupport}}
+<td>{{$b.VendorCooperationScore}}
+</td></tr>
+{{end -}}
+{{end -}}
+</table>
+<small>
+<sup>1</sup> ROM chip package (PLCC, DIP32, DIP8, SOIC8).<br />
+<sup>2</sup> ROM chip protocol/type (parallel flash, LPC, FWH, SPI).<br />
+<sup>3</sup> ROM chip socketed (Y/N)?<br />
+<sup>4</sup> Board supported by [http://www.flashrom.org flashrom] (Y/N)?<br />
+<sup>5</sup> Vendor Cooperation Score.<br />
+<sup>6</sup> [http://www.flashrom.org flashrom] does not work when the vendor BIOS is booted, but it does work when the machine is booted with coreboot.<br />
+<sup>7</sup> Some boards have ROM sockets, others are soldered.<br />
+</small>
+{{range $t := .Timeframes -}}
+<h1>{{$t}}</h1>
+{{range $l := index $data.Logs $t -}}
+{{if $l.Reference}}<span id="{{$l.VendorBoard}}"></span>
+{{end -}}
+<a href='https://www.coreboot.org/Board:{{$l.VendorBoard}}'>{{$l.VendorBoard}}</a> at {{$l.TimeReadable}}
+<a href='https://review.coreboot.org/plugins/gitiles/coreboot/+/{{$l.Upstream}}'>upstream tree</a> (
+{{range $f := $l.Files -}}
+<a href='https://review.coreboot.org/plugins/gitiles/board-status/+/refs/heads/master/{{$f.Path}}{{$f.Basename}}'>{{$f.Basename}}</a> {{/* */}}
+{{end -}}
+)<br />
+{{- end -}}
+{{end -}}
+{{/* */}}
+</body>
+</html>
diff --git a/util/docker/coreboot.org-status/board-status.html/types.go b/util/docker/coreboot.org-status/board-status.html/types.go
new file mode 100644
index 0000000000..93094d3857
--- /dev/null
+++ b/util/docker/coreboot.org-status/board-status.html/types.go
@@ -0,0 +1,60 @@
+package main
+
+import (
+ "io/fs"
+)
+
+type Board struct {
+ Vendor string
+ VendorNice string
+ Vendor2nd string
+ VendorBoard string
+ Board string
+ BoardNice string
+ BoardURL string
+ NorthbridgeNice string
+ SouthbridgeNice string
+ SuperIONice string
+ CPUNice string
+ SocketNice string
+ ROMPackage string
+ ROMProtocol string
+ ROMSocketed string
+ FlashromSupport string
+ VendorCooperationScore string
+ VendorCooperationPage string
+}
+
+type Path struct {
+ Path string
+ Basename string
+}
+
+type Log struct {
+ Reference bool
+ VendorBoard string
+ Time string
+ TimeReadable string
+ Upstream string
+ Files []Path
+}
+
+type Category string
+type Timeframe string
+
+type DateString string
+
+type TemplateData struct {
+ Categories []Category
+ CategoryNiceNames map[Category]string
+ BoardsByCategory map[Category][]Board
+ Timeframes []Timeframe
+ Logs map[Timeframe][]Log
+ VendorBoardDate map[string]DateString
+ VendorBoardReferenced map[string]bool
+}
+
+type NamedFS struct {
+ FS fs.FS
+ Name string
+}