]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Add devicetree-auto support for UKI
authorClayton Craft <clayton@craftyguy.net>
Tue, 26 Aug 2025 18:21:50 +0000 (11:21 -0700)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Fri, 12 Sep 2025 06:59:42 +0000 (08:59 +0200)
This renames Devicetree --> Devicetrees, and adds support for listing
globs and files as currently supported for KernelModule* options.
When multiple devicetrees are found, they are added to dtbauto sections
when building a UKI.
Multiple dtbs are not supported for type 1 booting.

Fixes #3827

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

index df81cd4e6b8535a43bb2c52eac776cc73a9dfd8f..30f8854b3249b75177ff17b7a15664741d978f56 100644 (file)
@@ -90,7 +90,13 @@ from mkosi.documentation import show_docs
 from mkosi.installer import clean_package_manager_metadata
 from mkosi.installer.pacman import Pacman
 from mkosi.installer.zypper import Zypper
-from mkosi.kmod import gen_required_kernel_modules, is_valid_kdir, loaded_modules, process_kernel_modules
+from mkosi.kmod import (
+    filter_devicetrees,
+    gen_required_kernel_modules,
+    is_valid_kdir,
+    loaded_modules,
+    process_kernel_modules,
+)
 from mkosi.log import ARG_DEBUG, complete_step, die, log_notice, log_step
 from mkosi.manifest import Manifest
 from mkosi.mounts import (
@@ -1597,21 +1603,6 @@ def build_kernel_modules_initrd(context: Context, kver: str) -> Path:
     return kmods
 
 
-def find_devicetree(context: Context, kver: str) -> Path:
-    assert context.config.devicetree
-
-    for d in (
-        context.root / f"usr/lib/firmware/{kver}/device-tree",
-        context.root / f"usr/lib/linux-image-{kver}",
-        context.root / f"usr/lib/modules/{kver}/dtb",
-    ):
-        dtb = d / context.config.devicetree
-        if dtb.exists():
-            return dtb
-
-    die(f"Requested devicetree {context.config.devicetree} not found")
-
-
 def want_signed_pcrs(config: Config) -> bool:
     return config.sign_expected_pcr == ConfigFeature.enabled or (
         config.sign_expected_pcr == ConfigFeature.auto
@@ -1769,10 +1760,13 @@ def build_uki(
         *flatten(["--ro-bind", os.fspath(profile), os.fspath(workdir(profile))] for profile in profiles),
     ]  # fmt: skip
 
-    if context.config.devicetree:
-        dtb = find_devicetree(context, kver)
-        arguments += ["--devicetree", workdir(dtb)]
-        options += ["--ro-bind", dtb, workdir(dtb)]
+    if context.config.devicetrees:
+        dtbs = filter_devicetrees(context.root, kver, include=context.config.devicetrees)
+        switch = "--devicetree" if len(dtbs) == 1 else "--devicetree-auto"
+        for dtb_rel in dtbs:
+            dtb = context.root / dtb_rel
+            arguments += [switch, workdir(dtb)]
+            options += ["--ro-bind", dtb, workdir(dtb)]
 
     if context.config.splash:
         splash = context.root / os.fspath(context.config.splash).lstrip("/")
@@ -2016,8 +2010,16 @@ def install_type1(
         entry.parent.mkdir(parents=True, exist_ok=True)
 
     dtb = None
-    if context.config.devicetree:
-        dtb = dst / context.config.devicetree
+    source_dtb = None
+    if context.config.devicetrees:
+        dtbs = filter_devicetrees(context.root, kver, include=context.config.devicetrees)
+        if len(dtbs) != 1:
+            die(
+                "Type 1 boot entries support only single devicetree, use UKI builds for multiple devicetrees"
+            )
+
+        source_dtb = context.root / dtbs[0]
+        dtb = dst / dtbs[0].relative_to(f"usr/lib/modules/{kver}/dtb")
         with umask(~0o700):
             dtb.parent.mkdir(parents=True, exist_ok=True)
 
@@ -2040,8 +2042,8 @@ def install_type1(
             Path(shutil.copy2(initrd, dst.parent / initrd.name)) for initrd in microcode + initrds
         ] + [Path(shutil.copy2(kmods, dst / "kernel-modules.initrd"))]
 
-        if dtb:
-            shutil.copy2(find_devicetree(context, kver), dtb)
+        if dtb and source_dtb:
+            shutil.copy2(source_dtb, dtb)
 
         with entry.open("w") as f:
             f.write(
index c006fd81c10f23951c53d71dfb82efcb3df00619..d61cc6f517c4c0f67608a9c79d4940f6997ca0f9 100644 (file)
@@ -2023,7 +2023,7 @@ class Config:
     initrd_packages: list[str]
     initrd_volatile_packages: list[str]
     microcode_host: bool
-    devicetree: Optional[str]
+    devicetrees: list[str]
     splash: Optional[Path]
     kernel_command_line: list[str]
     kernel_modules_include: list[str]
@@ -3184,10 +3184,11 @@ SETTINGS: list[ConfigSetting[Any]] = [
         help="Packages to install in the initrd that are not cached",
     ),
     ConfigSetting(
-        dest="devicetree",
+        dest="devicetrees",
         section="Content",
-        parse=config_parse_string,
-        help="Devicetree to be used by the booting kernel",
+        parse=config_make_list_parser(delimiter=","),
+        help="Devicetree(s) to be used by the booting kernel",
+        compat_names=("Devicetree",),
     ),
     ConfigSetting(
         dest="splash",
@@ -5493,7 +5494,7 @@ def summary(config: Config) -> str:
                     Initrd Profiles: {line_join_list(config.initrd_profiles)}
                     Initrd Packages: {line_join_list(config.initrd_packages)}
            Initrd Volatile Packages: {line_join_list(config.initrd_volatile_packages)}
-                         Devicetree: {none_to_none(config.devicetree)}
+                        Devicetrees: {line_join_list(config.devicetrees)}
                              Splash: {none_to_none(config.splash)}
                 Kernel Command Line: {line_join_list(config.kernel_command_line)}
                      Kernel Modules: {line_join_list(config.kernel_modules_include)}
index 8f1106bb1817b4ae0f1483e52a8ef412011a736e..2937666ba65b678652943b2a06d247856be939e2 100644 (file)
@@ -510,3 +510,43 @@ def is_valid_kdir(kdir: Path) -> bool:
 
     # check that kdir contains more than just updates
     return dircontent != [kdir / "updates"]
+
+
+def filter_devicetrees(
+    root: Path,
+    kver: str,
+    *,
+    include: Iterable[str],
+) -> list[Path]:
+    if not include:
+        return []
+
+    logging.debug(f"Devicetrees include: {' '.join(include)}")
+
+    # Search standard DTB locations
+    dtb_dirs = [
+        Path("usr/lib/firmware") / kver / "device-tree",
+        Path(f"usr/lib/linux-image-{kver}"),
+        Path("usr/lib/modules") / kver / "dtb",
+    ]
+
+    matched_dtbs = []
+    globs = list(include)
+
+    with chdir(root):
+        for dtb_dir in dtb_dirs:
+            all_dtbs = [p for p in dtb_dir.rglob("*.dtb") if p.is_file() or p.is_symlink()]
+            logging.debug(f"Found {len(all_dtbs)} DTB files in {dtb_dir}")
+
+            for dtb in all_dtbs:
+                rel_path = os.fspath(dtb.relative_to(dtb_dir))
+                if globs_match_filename(rel_path, globs):
+                    logging.debug(f"Matched DTB: {rel_path} in {dtb_dir}")
+                    matched_dtbs.append(dtb)
+
+    if not matched_dtbs:
+        logging.warning(f"Devicetrees patterns '{globs}' matched 0 files")
+    else:
+        logging.debug(f"Including {len(matched_dtbs)} devicetree files")
+
+    return sorted(matched_dtbs)
index e41663852f875c60aa4bde48a6fcd05424556467..84cfae16d287e5495f4b056eddd0ad90c215613f 100644 (file)
@@ -1110,11 +1110,17 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
 :   Similar to `VolatilePackages=`, except it applies to the default
     initrd.
 
-`Devicetree=`, `--devicetree=`
-:   When set, specifies a Devicetree blob to be used by the booting system,
-    instead of the one provided by firmware. **mkosi** will search for the
-    specified file relative to common paths where Linux distributions install
-    Devicetree files. It should typically have the format `<vendor>/<board>.dtb`.
+`Devicetrees=`, `--devicetrees=`
+:   Comma-separated list of devicetree patterns for automatic hardware-based selection.
+    Patterns are glob expressions. **mkosi** searches for devicetree files in standard
+    locations relative to `/usr/lib/modules/<kver>/dtb/`, `/usr/lib/firmware/<kver>/device-tree/`,
+    and `/usr/lib/linux-image-<kver>/`.
+
+    For UKI builds, multiple matches enable automatic hardware-based selection using
+    the `.dtbauto` sections. Type 1 boot entries require exactly one match.
+
+    Example: `Devicetrees=rockchip/*,imx.*` would include all Rockchip devicetrees
+    and any IMX devicetrees.
 
 `Splash=`, `--splash=`
 :   When set, the boot splash for any unified kernel image built by **mkosi** will
index 929ec2aeb7f20b685c8b094886edc1dde02bf466..36c5e46efd203a2c410aff3379d279f948b08461 100644 (file)
@@ -150,7 +150,9 @@ def test_config() -> None:
             "Dependencies": [
                 "dep1"
             ],
-            "Devicetree": "freescale/imx8mm-verdin-nonwifi-dev.dtb",
+            "Devicetrees": [
+                "freescale/imx8mm-verdin-nonwifi-dev.dtb"
+            ],
             "Distribution": "fedora",
             "Drives": [
                 {
@@ -511,7 +513,7 @@ def test_config() -> None:
         make_initrd=False,
         manifest_format=[ManifestFormat.json, ManifestFormat.changelog],
         microcode_host=True,
-        devicetree="freescale/imx8mm-verdin-nonwifi-dev.dtb",
+        devicetrees=["freescale/imx8mm-verdin-nonwifi-dev.dtb"],
         minimum_version="123",
         mirror=None,
         nspawn_settings=None,