m/node: build microcode payloads

This adds a builder for loadable microcode payloads for the Linux
kernel and microcode for Intel and AMD CPUs. It also adds a rule
generating a microcode payload for Metropolis at
//metropolis/node:ucode but does not integrate it yet.

Change-Id: I00145e4c983d9ff3e81881e92cbecc3e09392665
Reviewed-on: https://review.monogon.dev/c/monogon/+/546
Reviewed-by: Sergiusz Bazanski <serge@monogon.tech>
diff --git a/metropolis/node/build/mkucode/main.go b/metropolis/node/build/mkucode/main.go
new file mode 100644
index 0000000..1cd8960
--- /dev/null
+++ b/metropolis/node/build/mkucode/main.go
@@ -0,0 +1,71 @@
+// This assembles standalone microcode files into the format expected by the
+// Linux microcode loader. See
+// https://www.kernel.org/doc/html/latest/x86/microcode.html for further
+// information.
+package main
+
+import (
+	"flag"
+	"io"
+	"log"
+	"os"
+
+	"github.com/cavaliergopher/cpio"
+	"google.golang.org/protobuf/encoding/prototext"
+
+	"source.monogon.dev/metropolis/node/build/mkucode/spec"
+)
+
+var (
+	specPath = flag.String("spec", "", "Path to prototext specification (metropolis.node.build.mkucode.UCode)")
+	outPath  = flag.String("out", "", "Output path for cpio to be prepend to initrd")
+)
+
+// Usage: -spec <ucode.prototxt> -out <ucode.cpio>
+func main() {
+	flag.Parse()
+	specRaw, err := os.ReadFile(*specPath)
+	if err != nil {
+		log.Fatalf("Failed to read spec: %v", err)
+	}
+	var ucodeSpec spec.UCode
+	if err := prototext.Unmarshal(specRaw, &ucodeSpec); err != nil {
+		log.Fatalf("Failed unmarshaling ucode spec: %v", err)
+	}
+	out, err := os.Create(*outPath)
+	if err != nil {
+		log.Fatalf("Failed to create cpio: %v", err)
+	}
+	defer out.Close()
+	cpioWriter := cpio.NewWriter(out)
+	for _, vendor := range ucodeSpec.Vendor {
+		var totalSize int64
+		for _, file := range vendor.File {
+			data, err := os.Stat(file)
+			if err != nil {
+				log.Fatalf("Failed to stat file for vendor %q: %v", vendor.Id, err)
+			}
+			totalSize += data.Size()
+		}
+		if err := cpioWriter.WriteHeader(&cpio.Header{
+			Mode: 0444,
+			Name: "kernel/x86/microcode/" + vendor.Id + ".bin",
+			Size: totalSize,
+		}); err != nil {
+			log.Fatalf("Failed to write cpio header for vendor %q: %v", vendor.Id, err)
+		}
+		for _, file := range vendor.File {
+			f, err := os.Open(file)
+			if err != nil {
+				log.Fatalf("Failed to open file for vendor %q: %v", vendor.Id, err)
+			}
+			if _, err := io.Copy(cpioWriter, f); err != nil {
+				log.Fatalf("Failed to copy data for file %q: %v", file, err)
+			}
+			f.Close()
+		}
+	}
+	if err := cpioWriter.Close(); err != nil {
+		log.Fatalf("Failed writing cpio: %v", err)
+	}
+}