blob: 6ad2a9392b8a92e334591646e274c814bfbbbe04 [file] [log] [blame]
Lorenz Brun17c4c8b2022-02-01 12:59:47 +01001// fwprune is a buildsystem utility that filters linux-firmware repository
2// contents to include only files required by the built-in kernel modules,
3// that are specified in modules.builtin.modinfo.
4// (see: https://www.kernel.org/doc/Documentation/kbuild/kbuild.txt)
5package main
6
7import (
8 "bytes"
9 "log"
10 "os"
11 "path/filepath"
12 "sort"
13 "strings"
14
15 "google.golang.org/protobuf/encoding/prototext"
16
17 "source.monogon.dev/metropolis/node/build/fsspec"
18)
19
20// fwPaths returns a slice of filesystem paths relative to the root of the
21// linux-firmware repository, pointing at firmware files, according to contents
22// of the kernel build side effect: modules.builtin.modinfo.
23func fwPaths(mi []byte) []string {
24 // Use a map pset to deduplicate firmware paths.
25 pset := make(map[string]bool)
26 // Get a slice of entries of the form "unix.license=GPL" from mi. Then extract
27 // firmware information from it.
28 entries := bytes.Split(mi, []byte{0})
29 for _, entry := range entries {
30 // Skip empty entries.
31 if len(entry) == 0 {
32 continue
33 }
34 // Parse the entries. Split each entry into a key-value pair, separated
35 // by "=".
36 kv := strings.SplitN(string(entry), "=", 2)
37 key, value := kv[0], kv[1]
38 // Split the key into a module.attribute] pair, such as "unix.license".
39 ma := strings.SplitN(key, ".", 2)
40 // Skip, if it's not a firmware entry, according to the attribute.
41 if ma[1] != "firmware" {
42 continue
43 }
44 // If it is though, value holds a firmware path.
45 pset[value] = true
46 }
47 // Convert the deduplicated pset to a slice.
48 pslice := make([]string, 0, len(pset))
49 for p, _ := range pset {
50 pslice = append(pslice, p)
51 }
52 sort.Strings(pslice)
53 return pslice
54}
55
56// fwprune takes a modinfo file from the kernel and extracts a list of all
57// firmware files requested by all modules in that file. It then takes all
58// available firmware file paths (newline-separated in the firmwareList file)
59// and tries to match the requested file paths as a suffix of them.
60// For example if a module requests firmware foo/bar.bin and in the firmware list
61// there is a file at path build-out/x/y/foo/bar.bin it will use that file.
62// Finally it generates an fsspec placing each file under its requested path
63// under /lib/firmware.
64func main() {
65 if len(os.Args) != 4 {
66 log.Fatal("Usage: fwprune modules.builtin.modinfo firmwareListPath outSpec")
67 }
68 modinfo := os.Args[1]
69 firmwareListPath := os.Args[2]
70 outSpec := os.Args[3]
71
72 allFirmwareData, err := os.ReadFile(firmwareListPath)
73 if err != nil {
74 log.Fatalf("Failed to read firmware source list: %v", err)
75 }
76 allFirmwarePaths := strings.Split(string(allFirmwareData), "\n")
77
78 // Create a look-up table of all possible suffixes to their full paths as
79 // this is much faster at O(n) than calling strings.HasSuffix for every
80 // possible combination which is O(n^2).
81 suffixLUT := make(map[string]string)
82 for _, firmwarePath := range allFirmwarePaths {
83 pathParts := strings.Split(firmwarePath, string(os.PathSeparator))
84 for i := range pathParts {
85 suffixLUT[filepath.Join(pathParts[i:len(pathParts)]...)] = firmwarePath
86 }
87 }
88
89 // Get the firmware file paths used by modules according to modinfo data
90 mi, err := os.ReadFile(modinfo)
91 if err != nil {
92 log.Fatalf("While reading modinfo: %v", err)
93 }
94 fwp := fwPaths(mi)
95
96 var files []*fsspec.File
97
98 for _, p := range fwp {
99 sourcePath := suffixLUT[p]
100 if sourcePath == "" {
101 // This should not be fatal as sometimes linux-firmware cannot
102 // ship all firmware usable by the kernel for mostly legal reasons.
103 log.Printf("WARNING: Requested firmware %q not found", p)
104 continue
105 }
106 files = append(files, &fsspec.File{
107 Path: filepath.Join("/lib/firmware", p),
108 Mode: 0444,
109 SourcePath: sourcePath,
110 })
111 }
112 fsspecRaw, err := prototext.Marshal(&fsspec.FSSpec{File: files})
113 if err := os.WriteFile(outSpec, fsspecRaw, 0644); err != nil {
114 log.Fatalf("failed writing output: %v", err)
115 }
116}