Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

122 lines
4.0 KiB
Python
Raw Normal View History

from argparse import ArgumentParser
from dataclasses import dataclass
from functools import cached_property
import json
from pathlib import Path
import shutil
from libfdt import Fdt, FdtException, FDT_ERR_NOSPACE, FDT_ERR_NOTFOUND, fdt_overlay_apply
@dataclass
class Overlay:
name: str
filter: str
dtbo_file: Path
@cached_property
def fdt(self) -> Fdt:
with self.dtbo_file.open("rb") as fd:
return Fdt(fd.read())
@cached_property
def compatible(self) -> set[str]:
return get_compatible(self.fdt)
def get_compatible(fdt) -> set[str]:
root_offset = fdt.path_offset("/")
try:
return set(fdt.getprop(root_offset, "compatible").as_stringlist())
except FdtException as e:
if e.err == -FDT_ERR_NOTFOUND:
return set()
else:
raise e
def apply_overlay(dt: Fdt, dto: Fdt) -> Fdt:
while True:
# we need to copy the buffers because they can be left in an inconsistent state
# if the operation fails (ref: fdtoverlay source)
result = dt.as_bytearray().copy()
err = fdt_overlay_apply(result, dto.as_bytearray().copy())
if err == 0:
new_dt = Fdt(result)
# trim the extra space from the final tree
new_dt.pack()
return new_dt
if err == -FDT_ERR_NOSPACE:
# not enough space, add some blank space and try again
# magic number of more space taken from fdtoverlay
dt.resize(dt.totalsize() + 65536)
continue
raise FdtException(err)
def process_dtb(rel_path: Path, source: Path, destination: Path, overlays_data: list[Overlay]):
source_dt = source / rel_path
print(f"Processing source device tree {rel_path}...")
with source_dt.open("rb") as fd:
dt = Fdt(fd.read())
for overlay in overlays_data:
if overlay.filter and overlay.filter not in str(rel_path):
print(f" Skipping overlay {overlay.name}: filter does not match")
continue
dt_compatible = get_compatible(dt)
if len(dt_compatible) == 0:
print(f" Device tree {rel_path} has no compatible string set. Assuming it's compatible with overlay")
elif not overlay.compatible.intersection(dt_compatible):
print(f" Skipping overlay {overlay.name}: {overlay.compatible} is incompatible with {dt_compatible}")
continue
print(f" Applying overlay {overlay.name}")
dt = apply_overlay(dt, overlay.fdt)
print(f"Saving final device tree {rel_path}...")
dest_path = destination / rel_path
dest_path.parent.mkdir(parents=True, exist_ok=True)
with dest_path.open("wb") as fd:
fd.write(dt.as_bytearray())
def main():
parser = ArgumentParser(description='Apply a list of overlays to a directory of device trees')
parser.add_argument("--source", type=Path, help="Source directory")
parser.add_argument("--destination", type=Path, help="Destination directory")
parser.add_argument("--overlays", type=Path, help="JSON file with overlay descriptions")
args = parser.parse_args()
source: Path = args.source
destination: Path = args.destination
overlays: Path = args.overlays
with overlays.open() as fd:
overlays_data = [
Overlay(
name=item["name"],
filter=item["filter"],
dtbo_file=Path(item["dtboFile"]),
)
for item in json.load(fd)
]
for dirpath, dirnames, filenames in source.walk():
for filename in filenames:
rel_path = (dirpath / filename).relative_to(source)
if filename.endswith(".dtb"):
process_dtb(rel_path, source, destination, overlays_data)
else:
# Copy other files through
dest_path = destination / rel_path
dest_path.parent.mkdir(parents=True, exist_ok=True)
shutil.copy(source / rel_path, dest_path)
if __name__ == '__main__':
main()