]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Add FirmwareInclude= and FirmwareExclude= options
authorAntonio Alvarez Feijoo <antonio.feijoo@suse.com>
Fri, 7 Feb 2025 10:55:44 +0000 (11:55 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Fri, 7 Feb 2025 13:18:03 +0000 (14:18 +0100)
Rigth now, firmware added to the image is determined as a dependency of any
added kernel modules. However, a common use case is that a user may need to
force certain firmware to be included or excluded regardless of that dependency.

mkosi/__init__.py
mkosi/config.py
mkosi/kmod.py
mkosi/resources/man/mkosi.1.md
tests/test_json.py

index 997a4fa96063b73c2b280f7c4eb8fb97c94d7a84..d0d77b88c6db2f2e1f311131127d7995c60eab2e 100644 (file)
@@ -1487,12 +1487,14 @@ def build_kernel_modules_initrd(context: Context, kver: str) -> Path:
         files=gen_required_kernel_modules(
             context,
             kver,
-            include=finalize_kernel_modules_include(
+            modules_include=finalize_kernel_modules_include(
                 context,
                 include=context.config.kernel_modules_initrd_include,
                 host=context.config.kernel_modules_initrd_include_host,
             ),
-            exclude=context.config.kernel_modules_initrd_exclude,
+            modules_exclude=context.config.kernel_modules_initrd_exclude,
+            firmware_include=context.config.firmware_include,
+            firmware_exclude=context.config.firmware_exclude,
         ),
         sandbox=context.sandbox,
     )
@@ -2858,12 +2860,14 @@ def run_depmod(context: Context, *, cache: bool = False) -> None:
             process_kernel_modules(
                 context,
                 kver,
-                include=finalize_kernel_modules_include(
+                modules_include=finalize_kernel_modules_include(
                     context,
                     include=context.config.kernel_modules_include,
                     host=context.config.kernel_modules_include_host,
                 ),
-                exclude=context.config.kernel_modules_exclude,
+                modules_exclude=context.config.kernel_modules_exclude,
+                firmware_include=context.config.firmware_include,
+                firmware_exclude=context.config.firmware_exclude,
             )
 
     if context.config.output_format.is_extension_or_portable_image():
index 8f6750fd25d3b6e0a80035f64afc5ecd623bbf0d..7d46d0339ee3928ddc1351323de74b45f9462355 100644 (file)
@@ -1858,6 +1858,8 @@ class Config:
     kernel_modules_include: list[str]
     kernel_modules_exclude: list[str]
     kernel_modules_include_host: bool
+    firmware_include: list[str]
+    firmware_exclude: list[str]
 
     kernel_modules_initrd: bool
     kernel_modules_initrd_include: list[str]
@@ -2895,6 +2897,20 @@ SETTINGS: list[ConfigSetting[Any]] = [
         parse=config_make_list_parser(delimiter=","),
         help="When building a kernel modules initrd, exclude the specified kernel modules",
     ),
+    ConfigSetting(
+        dest="firmware_include",
+        metavar="REGEX",
+        section="Content",
+        parse=config_make_list_parser(delimiter=","),
+        help="Include the specified firmware in the image",
+    ),
+    ConfigSetting(
+        dest="firmware_exclude",
+        metavar="REGEX",
+        section="Content",
+        parse=config_make_list_parser(delimiter=","),
+        help="Exclude the specified firmware from the image",
+    ),
     ConfigSetting(
         dest="locale",
         section="Content",
@@ -4907,6 +4923,8 @@ def summary(config: Config) -> str:
              Kernel Modules Include: {line_join_list(config.kernel_modules_include)}
              Kernel Modules Exclude: {line_join_list(config.kernel_modules_exclude)}
         Kernel Modules Include Host: {yes_no(config.kernel_modules_include_host)}
+                   Firmware Include: {line_join_list(config.firmware_include)}
+                   Firmware Exclude: {line_join_list(config.firmware_exclude)}
 
               Kernel Modules Initrd: {yes_no(config.kernel_modules_initrd)}
       Kernel Modules Initrd Include: {line_join_list(config.kernel_modules_initrd_include)}
index a25bb52676a0ca5dfa42c572d864b8ccb9248b68..e591b953c9ee00076f5e796f1e0a651e49037426 100644 (file)
@@ -55,6 +55,39 @@ def filter_kernel_modules(
     return sorted(modules)
 
 
+def filter_firmware(
+    root: Path,
+    firmware: set[Path],
+    *,
+    include: Iterable[str],
+    exclude: Iterable[str],
+) -> set[Path]:
+    if not include and not exclude:
+        return firmware
+
+    if exclude:
+        remove = set()
+        regex = re.compile("|".join(exclude))
+        for f in firmware:
+            rel = os.fspath(Path(*f.parts[3:]))
+            if regex.search(rel):
+                remove.add(f)
+
+        firmware -= remove
+
+    if include:
+        firmwared = Path("usr/lib/firmware")
+        with chdir(root):
+            all_firmware = set(firmwared.rglob("*"))
+        regex = re.compile("|".join(include))
+        for f in all_firmware:
+            rel = os.fspath(Path(*f.parts[3:]))
+            if regex.search(rel):
+                firmware.add(f)
+
+    return firmware
+
+
 def normalize_module_name(name: str) -> str:
     return name.replace("_", "-")
 
@@ -175,16 +208,19 @@ def gen_required_kernel_modules(
     context: Context,
     kver: str,
     *,
-    include: Iterable[str],
-    exclude: Iterable[str],
+    modules_include: Iterable[str],
+    modules_exclude: Iterable[str],
+    firmware_include: Iterable[str],
+    firmware_exclude: Iterable[str],
 ) -> Iterator[Path]:
     modulesd = Path("usr/lib/modules") / kver
+    firmwared = Path("usr/lib/firmware")
 
     # There is firmware in /usr/lib/firmware that is not depended on by any modules so if any firmware was
     # installed we have to take the slow path to make sure we don't copy firmware into the initrd that is not
     # depended on by any kernel modules.
-    if exclude or (context.root / "usr/lib/firmware").glob("*"):
-        modules = filter_kernel_modules(context.root, kver, include=include, exclude=exclude)
+    if modules_exclude or (context.root / firmwared).glob("*"):
+        modules = filter_kernel_modules(context.root, kver, include=modules_include, exclude=modules_exclude)
         names = [module_path_to_name(m) for m in modules]
         mods, firmware = resolve_module_dependencies(context, kver, names)
     else:
@@ -195,6 +231,9 @@ def gen_required_kernel_modules(
             mods = set(modulesd.rglob("*.ko*"))
         firmware = set()
 
+    # Include or exclude firmware explicitly configured
+    firmware = filter_firmware(context.root, firmware, include=firmware_include, exclude=firmware_exclude)
+
     # Some firmware dependencies are symbolic links, so the targets for those must be included in the list
     # of required firmware files too. Intermediate symlinks are not included, and so links pointing to links
     # results in dangling symlinks in the final image.
@@ -232,17 +271,28 @@ def process_kernel_modules(
     context: Context,
     kver: str,
     *,
-    include: Iterable[str],
-    exclude: Iterable[str],
+    modules_include: Iterable[str],
+    modules_exclude: Iterable[str],
+    firmware_include: Iterable[str],
+    firmware_exclude: Iterable[str],
 ) -> None:
-    if not exclude:
+    if not modules_exclude and not firmware_exclude:
         return
 
     modulesd = Path("usr/lib/modules") / kver
     firmwared = Path("usr/lib/firmware")
 
     with complete_step("Applying kernel module filters"):
-        required = set(gen_required_kernel_modules(context, kver, include=include, exclude=exclude))
+        required = set(
+            gen_required_kernel_modules(
+                context,
+                kver,
+                modules_include=modules_include,
+                modules_exclude=modules_exclude,
+                firmware_include=firmware_include,
+                firmware_exclude=firmware_exclude,
+            )
+        )
 
         with chdir(context.root):
             modules = sorted(modulesd.rglob("*.ko*"), reverse=True)
index 775b5096b1d2f9598c49fdb566850520af2f3475..d6a4e8fe31cc30900e5c171d4bbddefa507e1f7b 100644 (file)
@@ -1052,6 +1052,17 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
 `KernelModulesInitrdExclude=`, `--kernel-modules-initrd-exclude=`
 :   Like `KernelModulesExclude=`, but applies to the kernel modules included in the kernel modules initrd.
 
+`FirmwareInclude=`, `--firmware-include=`
+:   Takes a list of regex patterns that specify firmware files to include in the image. Patterns should be
+    relative to `/usr/lib/firmware/<subdir>` paths. **mkosi** checks for a match anywhere in the firmware path
+    (e.g. `bcm8483` will match against `cxgb4/bcm8483.bin`). All firmware files that match any of the specified
+    patterns are included in the image.
+
+`FirmwareExclude=`, `--firmware-exclude=`
+:   Takes a list of regex patterns that specify firmware files to exclude from the image. Behaves the same as
+    `FirmwareInclude=` except that all firmware that match any of the specified patterns is excluded from the
+    image. Firmware specified with this option is excluded even if an included kernel module depends on it.
+
 `Locale=`, `--locale=`, `LocaleMessages=`, `--locale-messages=`, `Keymap=`, `--keymap=`, `Timezone=`, `--timezone=`, `Hostname=`, `--hostname=`, `RootShell=`, `--root-shell=`
 :   The settings `Locale=`, `--locale=`, `LocaleMessages=`, `--locale-messages=`,
     `Keymap=`, `--keymap=`, `Timezone=`, `--timezone=`, `Hostname=`,
index 96d5f050c9b65d25816f3e6418adf8c33daba91b..ad096794465e26021c6f0cc75902d948cae8003a 100644 (file)
@@ -171,6 +171,12 @@ def test_config() -> None:
             "Files": [],
             "FinalizeScripts": [],
             "Firmware": "linux",
+            "FirmwareExclude": [
+                "brcm/"
+            ],
+            "FirmwareInclude": [
+                "ath3k-1"
+            ],
             "FirmwareVariables": "/foo/bar",
             "Format": "uki",
             "ForwardJournal": "/mkosi.journal",
@@ -451,6 +457,8 @@ def test_config() -> None:
         extra_trees=[],
         files=[],
         finalize_scripts=[],
+        firmware_exclude=["brcm/"],
+        firmware_include=["ath3k-1"],
         firmware_variables=Path("/foo/bar"),
         firmware=Firmware.linux,
         forward_journal=Path("/mkosi.journal"),