deviceTree.applyOverlays: rewrite in Python/libfdt (#341883)

authored by

K900 and committed by
GitHub
f0297c4e 24823604

+108 -36
+102
pkgs/os-specific/linux/device-tree/apply_overlays.py
···
··· 1 + from argparse import ArgumentParser 2 + from dataclasses import dataclass 3 + from functools import cached_property 4 + import json 5 + from pathlib import Path 6 + 7 + from libfdt import Fdt, FdtException, FDT_ERR_NOSPACE, fdt_overlay_apply 8 + 9 + 10 + @dataclass 11 + class Overlay: 12 + name: str 13 + filter: str 14 + dtbo_file: Path 15 + 16 + @cached_property 17 + def fdt(self): 18 + with self.dtbo_file.open("rb") as fd: 19 + return Fdt(fd.read()) 20 + 21 + @cached_property 22 + def compatible(self): 23 + return get_compatible(self.fdt) 24 + 25 + 26 + def get_compatible(fdt): 27 + root_offset = fdt.path_offset("/") 28 + return set(fdt.getprop(root_offset, "compatible").as_stringlist()) 29 + 30 + 31 + def apply_overlay(dt: Fdt, dto: Fdt) -> Fdt: 32 + while True: 33 + # we need to copy the buffers because they can be left in an inconsistent state 34 + # if the operation fails (ref: fdtoverlay source) 35 + result = dt.as_bytearray().copy() 36 + err = fdt_overlay_apply(result, dto.as_bytearray().copy()) 37 + 38 + if err == 0: 39 + new_dt = Fdt(result) 40 + # trim the extra space from the final tree 41 + new_dt.pack() 42 + return new_dt 43 + 44 + if err == -FDT_ERR_NOSPACE: 45 + # not enough space, add some blank space and try again 46 + # magic number of more space taken from fdtoverlay 47 + dt.resize(dt.totalsize() + 65536) 48 + continue 49 + 50 + raise FdtException(err) 51 + 52 + def main(): 53 + parser = ArgumentParser(description='Apply a list of overlays to a directory of device trees') 54 + parser.add_argument("--source", type=Path, help="Source directory") 55 + parser.add_argument("--destination", type=Path, help="Destination directory") 56 + parser.add_argument("--overlays", type=Path, help="JSON file with overlay descriptions") 57 + args = parser.parse_args() 58 + 59 + source: Path = args.source 60 + destination: Path = args.destination 61 + overlays: Path = args.overlays 62 + 63 + with overlays.open() as fd: 64 + overlays_data = [ 65 + Overlay( 66 + name=item["name"], 67 + filter=item["filter"], 68 + dtbo_file=Path(item["dtboFile"]), 69 + ) 70 + for item in json.load(fd) 71 + ] 72 + 73 + for source_dt in source.glob("**/*.dtb"): 74 + rel_path = source_dt.relative_to(source) 75 + 76 + print(f"Processing source device tree {rel_path}...") 77 + with source_dt.open("rb") as fd: 78 + dt = Fdt(fd.read()) 79 + 80 + dt_compatible = get_compatible(dt) 81 + 82 + for overlay in overlays_data: 83 + if overlay.filter and overlay.filter not in str(rel_path): 84 + print(f" Skipping overlay {overlay.name}: filter does not match") 85 + continue 86 + 87 + if not overlay.compatible.intersection(dt_compatible): 88 + print(f" Skipping overlay {overlay.name}: {overlay.compatible} is incompatible with {dt_compatible}") 89 + continue 90 + 91 + print(f" Applying overlay {overlay.name}") 92 + dt = apply_overlay(dt, overlay.fdt) 93 + 94 + print(f"Saving final device tree {rel_path}...") 95 + dest_path = destination / rel_path 96 + dest_path.parent.mkdir(parents=True, exist_ok=True) 97 + with dest_path.open("wb") as fd: 98 + fd.write(dt.as_bytearray()) 99 + 100 + 101 + if __name__ == '__main__': 102 + main()
+6 -36
pkgs/os-specific/linux/device-tree/default.nix
··· 1 - { lib, stdenv, stdenvNoCC, dtc }: 2 3 { 4 # Compile single Device Tree overlay source ··· 26 27 applyOverlays = (base: overlays': stdenvNoCC.mkDerivation { 28 name = "device-tree-overlays"; 29 - nativeBuildInputs = [ dtc ]; 30 - buildCommand = let 31 - overlays = lib.toList overlays'; 32 - in '' 33 - mkdir -p $out 34 - cd "${base}" 35 - find -L . -type f -name '*.dtb' -print0 \ 36 - | xargs -0 cp -v --no-preserve=mode --target-directory "$out" --parents 37 - 38 - for dtb in $(find "$out" -type f -name '*.dtb'); do 39 - dtbCompat=$(fdtget -t s "$dtb" / compatible 2>/dev/null || true) 40 - # skip files without `compatible` string 41 - test -z "$dtbCompat" && continue 42 - 43 - ${lib.flip (lib.concatMapStringsSep "\n") overlays (o: '' 44 - overlayCompat="$(fdtget -t s "${o.dtboFile}" / compatible)" 45 - 46 - # skip incompatible and non-matching overlays 47 - if [[ ! "$dtbCompat" =~ "$overlayCompat" ]]; then 48 - echo "Skipping overlay ${o.name}: incompatible with $(basename "$dtb")" 49 - elif ${if (o.filter == null) then "false" else '' 50 - [[ "''${dtb//${o.filter}/}" == "$dtb" ]] 51 - ''} 52 - then 53 - echo "Skipping overlay ${o.name}: filter does not match $(basename "$dtb")" 54 - else 55 - echo -n "Applying overlay ${o.name} to $(basename "$dtb")... " 56 - mv "$dtb"{,.in} 57 - fdtoverlay -o "$dtb" -i "$dtb.in" "${o.dtboFile}" 58 - echo "ok" 59 - rm "$dtb.in" 60 - fi 61 - '')} 62 - 63 - done 64 ''; 65 }); 66 }
··· 1 + { lib, stdenv, stdenvNoCC, dtc, writers, python3 }: 2 3 { 4 # Compile single Device Tree overlay source ··· 26 27 applyOverlays = (base: overlays': stdenvNoCC.mkDerivation { 28 name = "device-tree-overlays"; 29 + nativeBuildInputs = [ 30 + (python3.pythonOnBuildForHost.withPackages(ps: [ps.libfdt])) 31 + ]; 32 + buildCommand = '' 33 + python ${./apply_overlays.py} --source ${base} --destination $out --overlays ${writers.writeJSON "overlays.json" overlays'} 34 ''; 35 }); 36 }