m/b/fwprune: init
This adds a utility to filter out firmware that's actually used by the
kernel builtins.
Change-Id: If622ee8c5b056c9a75f1ca97bb1e40ae62cdf722
Reviewed-on: https://review.monogon.dev/c/monogon/+/506
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
diff --git a/metropolis/build/fwprune/main.go b/metropolis/build/fwprune/main.go
new file mode 100644
index 0000000..5791b9e
--- /dev/null
+++ b/metropolis/build/fwprune/main.go
@@ -0,0 +1,138 @@
+// fwprune is a buildsystem utility that filters linux-firmware repository
+// contents to include only files required by the built-in kernel modules,
+// that are specified in modules.builtin.modinfo.
+// (see: https://www.kernel.org/doc/Documentation/kbuild/kbuild.txt)
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+)
+
+// fwPaths returns a slice of filesystem paths relative to the root of the
+// linux-firmware repository, pointing at firmware files, according to contents
+// of the kernel build side effect: modules.builtin.modinfo.
+func fwPaths(mi []byte) []string {
+ // Use a map pset to deduplicate firmware paths.
+ pset := make(map[string]bool)
+ // Get a slice of entries of the form "unix.license=GPL" from mi. Then extract
+ // firmware information from it.
+ entries := bytes.Split(mi, []byte{0})
+ for _, entry := range entries {
+ // Skip empty entries.
+ if len(entry) == 0 {
+ continue
+ }
+ // Parse the entries. Split each entry into a key-value pair, separated
+ // by "=".
+ kv := strings.SplitN(string(entry), "=", 2)
+ key, value := kv[0], kv[1]
+ // Split the key into a module.attribute] pair, such as "unix.license".
+ ma := strings.SplitN(key, ".", 2)
+ // Skip, if it's not a firmware entry, according to the attribute.
+ if ma[1] != "firmware" {
+ continue
+ }
+ // If it is though, value holds a firmware path.
+ pset[value] = true
+ }
+ // Convert the deduplicated pset to a slice.
+ pslice := make([]string, 0, len(pset))
+ for p, _ := range pset {
+ pslice = append(pslice, p)
+ }
+ sort.Strings(pslice)
+ return pslice
+}
+
+// fwDirs returns a slice of filesystem paths relative to the root of
+// linux-firmware repository, pointing at directories that need to exist before
+// files specified by fwp paths can be created.
+func fwDirs(fwp []string) []string {
+ // Use a map dset to deduplicate directory paths.
+ dset := make(map[string]bool)
+ for _, p := range fwp {
+ dp := filepath.Dir(p)
+ dset[dp] = true
+ }
+ // Convert dset to a slice.
+ dslice := make([]string, 0, len(dset))
+ for d, _ := range dset {
+ dslice = append(dslice, d)
+ }
+ sort.Strings(dslice)
+ return dslice
+}
+
+// copyFile copies a file at filesystem path src to dst. dst must not point to
+// an existing file. It may return an IO error.
+func copyFile(dst, src string) error {
+ i, err := os.Open(src)
+ if err != nil {
+ return err
+ }
+ defer i.Close()
+
+ o, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE, 0770)
+ if err != nil {
+ return err
+ }
+ defer o.Close()
+
+ if _, err := io.Copy(o, i); err != nil {
+ return err
+ }
+ return nil
+}
+
+func main() {
+ // The directory at fwdst will be filled with firmware required by the kernel
+ // builtins specified in modules.builtin.modinfo [1]. fwsrc must point to the
+ // linux-firmware repository [2]. All parameters must be filesystem paths. The
+ // necessary parts of the original directory layout will be recreated at fwdst.
+ // fwprune will output a list of directories and files it creates.
+ // [1] https://www.kernel.org/doc/Documentation/kbuild/kbuild.txt
+ // [2] https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git
+ if len(os.Args) != 4 {
+ // Print usage information, if misused.
+ fmt.Println("Usage: fwprune modules.builtin.modinfo fwsrc fwdst")
+ os.Exit(1)
+ }
+ modinfo := os.Args[1]
+ fwsrc := os.Args[2]
+ fwdst := os.Args[3]
+
+ // Get the firmware file paths.
+ mi, err := os.ReadFile(modinfo)
+ if err != nil {
+ log.Fatalf("While reading modinfo: %v", err)
+ }
+ fwp := fwPaths(mi)
+
+ // Recreate the necessary parts of the linux-firmware directory tree.
+ fwd := fwDirs(fwp)
+ for _, rd := range fwd {
+ d := filepath.Join(fwdst, rd)
+ if err := os.MkdirAll(d, 0770); err != nil {
+ log.Fatalf("Couldn't create a subdirectory: %v", err)
+ }
+ fmt.Println(d)
+ }
+
+ // Copy the files specified by fwp.
+ for _, p := range fwp {
+ dst := filepath.Join(fwdst, p)
+ src := filepath.Join(fwsrc, p)
+
+ if err := copyFile(dst, src); err != nil {
+ log.Fatalf("Couldn't provide %q: %v", dst, err)
+ }
+ fmt.Println(p)
+ }
+}