summaryrefslogtreecommitdiff
path: root/util/docker/coreboot.org-status
diff options
context:
space:
mode:
authorPatrick Georgi <pgeorgi@google.com>2021-12-07 17:47:45 +0100
committerPatrick Georgi <patrick@coreboot.org>2022-11-03 13:50:30 +0000
commit3d0303a57c08bf8c56bb55885f6705680097bd27 (patch)
treedf4b5cdafb42223e11b0f532b0388f4e3a5c1758 /util/docker/coreboot.org-status
parent5318d9c9d13b39908b03b8184184fd913221b71e (diff)
util/docker/coreboot.org-status: Rewrite parser
The current tool is a shell script that mixes data collection and HTML generation and is generally a pain to work with. It takes 15 minutes to run. The new tool is written in go, collects all data first, then generates the output HTML from the data and a single template, and finishes in 10 seconds. The goal in this version is to produce output as similar as possible to the output of the shell script. Some difference will remain because the shell script returns some trash data whose reproduction would require more effort than is worth. Change-Id: I4fab86d24088e4f9eff434c21ce9caa077f3f9e2 Signed-off-by: Patrick Georgi <pgeorgi@google.com> Reviewed-on: https://review.coreboot.org/c/coreboot/+/59958 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Maxim Polyakov <max.senia.poliak@gmail.com>
Diffstat (limited to 'util/docker/coreboot.org-status')
-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
+}