diff options
author | Tom Rini <trini@konsulko.com> | 2024-07-29 10:22:42 -0600 |
---|---|---|
committer | Tom Rini <trini@konsulko.com> | 2024-07-29 10:22:42 -0600 |
commit | 9268c7a5e3e91564739bf0fdfd1df228a0273945 (patch) | |
tree | aa993c364934c571b00e46df077a67559d98a77c | |
parent | 9366640449ac3ef702b624e2f6f7604754a9082b (diff) | |
parent | 20e5399a311eb3aca5cebfd3e494047ed1ceacb8 (diff) | |
download | u-boot-9268c7a5e3e91564739bf0fdfd1df228a0273945.zip u-boot-9268c7a5e3e91564739bf0fdfd1df228a0273945.tar.gz u-boot-9268c7a5e3e91564739bf0fdfd1df228a0273945.tar.bz2 |
Merge tag 'dm-pull-29jul24' of https://gitlab.denx.de/u-boot/custodians/u-boot-dm
qconfig minor improvement
binman support for alternate entries
33 files changed, 1243 insertions, 107 deletions
@@ -1388,9 +1388,12 @@ cmd_binman = $(srctree)/tools/binman/binman $(if $(BINMAN_DEBUG),-D) \ -a rockchip-tpl-path=$(ROCKCHIP_TPL) \ -a spl-bss-pad=$(if $(CONFIG_SPL_SEPARATE_BSS),,1) \ -a tpl-bss-pad=$(if $(CONFIG_TPL_SEPARATE_BSS),,1) \ + -a vpl-bss-pad=$(if $(CONFIG_VPL_SEPARATE_BSS),,1) \ -a spl-dtb=$(CONFIG_SPL_OF_REAL) \ -a tpl-dtb=$(CONFIG_TPL_OF_REAL) \ + -a vpl-dtb=$(CONFIG_VPL_OF_REAL) \ -a pre-load-key-path=${PRE_LOAD_KEY_PATH} \ + -a of-spl-remove-props=$(CONFIG_OF_SPL_REMOVE_PROPS) \ $(BINMAN_$(@F)) OBJCOPYFLAGS_u-boot.ldr.hex := -I binary -O ihex diff --git a/doc/develop/qconfig.rst b/doc/develop/qconfig.rst index 8efb1eb..123779e 100644 --- a/doc/develop/qconfig.rst +++ b/doc/develop/qconfig.rst @@ -85,6 +85,20 @@ example, to find boards which enabled CONFIG_SCSI but not CONFIG_BLK:: 3 matches pg_wcom_seli8_defconfig highbank_defconfig pg_wcom_expu1_defconfig +It is also possible to search for particular values. For example, this finds all +boards with an empty string for `CONFIG_DEFAULT_FDT_FILE`:: + + ./tools/qconfig.py -f DEFAULT_FDT_FILE=\"\" + 1092 matches + ... + +This finds boards which have a value for SYS_MAXARGS other than 64:: + + ./tools/qconfig.py -f ~SYS_MAXARGS=64 + cfg CONFIG_SYS_MAXARGS + 281 matches + ... + Finding implied CONFIGs ----------------------- diff --git a/doc/develop/spl.rst b/doc/develop/spl.rst index 0a3e572..4bb48e6 100644 --- a/doc/develop/spl.rst +++ b/doc/develop/spl.rst @@ -121,6 +121,8 @@ Use `spl_phase()` to find the current U-Boot phase, e.g. `PHASE_SPL`. You can also find the previous and next phase and get the phase name. +.. _fdtgrep_filter: + Device tree ----------- The U-Boot device tree is filtered by the fdtgrep tools during the build diff --git a/scripts/basic/fixdep.c b/scripts/basic/fixdep.c index 5ced0f6..3d40bd7 100644 --- a/scripts/basic/fixdep.c +++ b/scripts/basic/fixdep.c @@ -421,6 +421,8 @@ int main(int argc, char *argv[]) strcpy(tmp_buf, "SPL_"); else if (!strncmp(target, "tpl/", 4)) strcpy(tmp_buf, "TPL_"); + else if (!strncmp(target, "vpl/", 4)) + strcpy(tmp_buf, "VPL_"); /* end U-Boot hack */ xprintf("cmd_%s := %s\n\n", target, cmdline); diff --git a/tools/binman/binman.rst b/tools/binman/binman.rst index 872e974..0cafc36 100644 --- a/tools/binman/binman.rst +++ b/tools/binman/binman.rst @@ -1211,7 +1211,7 @@ Templates provide a simple way to handle this:: spi-image { filename = "image-spi.bin"; - insert-template = <&fit>; + insert-template = <&common_part>; /* things specific to SPI follow */ footer { @@ -1224,7 +1224,7 @@ Templates provide a simple way to handle this:: mmc-image { filename = "image-mmc.bin"; - insert-template = <&fit>; + insert-template = <&common_part>; /* things specific to MMC follow */ footer { diff --git a/tools/binman/bintools.rst b/tools/binman/bintools.rst index 1336f4d..cd05ad8 100644 --- a/tools/binman/bintools.rst +++ b/tools/binman/bintools.rst @@ -10,6 +10,15 @@ binaries. It is fairly easy to create new bintools. Just add a new file to the +Bintool: bootgen: Generate bootable fsbl image for zynq/zynqmp +-------------------------------------------------------------- + +This bintools supports running Xilinx "bootgen" in order +to generate a bootable, authenticated image form an SPL. + + + + Bintool: bzip2: Compression/decompression using the bzip2 algorithm ------------------------------------------------------------------- @@ -37,6 +46,33 @@ Documentation about CBFS is at https://www.coreboot.org/CBFS +Bintool: cst: Image generation for U-Boot +----------------------------------------- + +This bintool supports running `cst` with some basic parameters as +needed by binman. + + + +Bintool: fdt_add_pubkey: Add public key to control dtb (spl or u-boot proper) +----------------------------------------------------------------------------- + +This bintool supports running `fdt_add_pubkey`. + +Normally mkimage adds signature information to the control dtb. However +binman images are built independent from each other. Thus it is required +to add the public key separately from mkimage. + + + +Bintool: fdtgrep: Handles the 'fdtgrep' tool +-------------------------------------------- + +This bintool supports running `fdtgrep` with parameters suitable for +producing SPL devicetrees from the main one. + + + Bintool: fiptool: Image generation for ARM Trusted Firmware ----------------------------------------------------------- @@ -143,11 +179,20 @@ Documentation is available via:: +Bintool: mkeficapsule: Handles the 'mkeficapsule' tool +------------------------------------------------------ + +This bintool is used for generating the EFI capsules. The +capsule generation parameters can either be specified through +commandline, or through a config file. + + + Bintool: mkimage: Image generation for U-Boot --------------------------------------------- This bintool supports running `mkimage` with some basic parameters as -neeed by binman. +needed by binman. Normally binman uses the mkimage built by U-Boot. But when run outside the U-Boot build system, binman can use the version installed in your system. @@ -194,25 +239,3 @@ Documentation is available via:: -Bintool: fdt_add_pubkey: Add public key to device tree ------------------------------------------------------- - -This bintool supports running `fdt_add_pubkey` in order to add a public -key coming from a certificate to a device-tree. - -Normally signing is done using `mkimage` in context of `binman sign`. However, -in this process the public key is not added to the stage before u-boot proper. -Using `fdt_add_pubkey` the key can be injected to the SPL independent of -`mkimage` - - - -Bintool: bootgen: Sign ZynqMP FSBL image ----------------------------------------- - -This bintool supports running `bootgen` in order to sign a SPL for ZynqMP -devices. - -The bintool automatically creates an appropriate input image file (.bif) for -bootgen based on the passed arguments. The output is a bootable, -authenticated `boot.bin` file. diff --git a/tools/binman/btool/fdt_add_pubkey.py b/tools/binman/btool/fdt_add_pubkey.py index a507742..75a9716 100644 --- a/tools/binman/btool/fdt_add_pubkey.py +++ b/tools/binman/btool/fdt_add_pubkey.py @@ -32,6 +32,10 @@ class Bintoolfdt_add_pubkey(bintool.Bintool): verified for the image / configuration to be considered valid. algo (str): Cryptographic algorithm. Optional parameter, default value: sha1,rsa2048 + + Returns: + CommandResult: Resulting output from the bintool, or None if the + tool is not present """ args = [] if algo: diff --git a/tools/binman/btool/fdtgrep.py b/tools/binman/btool/fdtgrep.py new file mode 100644 index 0000000..da1f8c7 --- /dev/null +++ b/tools/binman/btool/fdtgrep.py @@ -0,0 +1,137 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright 2022 Google LLC +# +"""Bintool implementation for fdtgrep + +fdtgrepprovides a way to grep devicetree-binary files to extract or remove +certain elements. + +Usage: fdtgrep - extract portions from device tree + +Usage: + fdtgrep <options> <dt file>|- + +Output formats are: + dts - device tree soure text + dtb - device tree blob (sets -Hmt automatically) + bin - device tree fragment (may not be a valid .dtb) + +Options: -[haAc:b:C:defg:G:HIlLmn:N:o:O:p:P:rRsStTvhV] + -a, --show-address Display address + -A, --colour Show all nodes/tags, colour those that match + -b, --include-node-with-prop <arg> Include contains containing property + -c, --include-compat <arg> Compatible nodes to include in grep + -C, --exclude-compat <arg> Compatible nodes to exclude in grep + -d, --diff Diff: Mark matching nodes with +, others with - + -e, --enter-node Enter direct subnode names of matching nodes + -f, --show-offset Display offset + -g, --include-match <arg> Node/property/compatible string to include in grep + -G, --exclude-match <arg> Node/property/compatible string to exclude in grep + -H, --show-header Output a header + -I, --show-version Put "/dts-v1/;" on first line of dts output + -l, --list-regions Output a region list + -L, --list-strings List strings in string table + -m, --include-mem Include mem_rsvmap section in binary output + -n, --include-node <arg> Node to include in grep + -N, --exclude-node <arg> Node to exclude in grep + -p, --include-prop <arg> Property to include in grep + -P, --exclude-prop <arg> Property to exclude in grep + -r, --remove-strings Remove unused strings from string table + -R, --include-root Include root node and all properties + -s, --show-subnodes Show all subnodes matching nodes + -S, --skip-supernodes Don't include supernodes of matching nodes + -t, --show-stringtab Include string table in binary output + -T, --show-aliases Include matching aliases in output + -o, --out <arg> -o <output file> + -O, --out-format <arg> -O <output format> + -v, --invert-match Invert the sense of matching (select non-matching lines) + -h, --help Print this help and exit + -V, --version Print version and exit +""" + +import tempfile + +from u_boot_pylib import tools +from binman import bintool + +class Bintoolfdtgrep(bintool.Bintool): + """Handles the 'fdtgrep' tool + + This bintool supports running `fdtgrep` with parameters suitable for + producing SPL devicetrees from the main one. + """ + def __init__(self, name): + super().__init__(name, 'Grep devicetree files') + + def create_for_phase(self, infile, phase, outfile, remove_props): + """Create the FDT for a particular phase + + Args: + infile (str): Input filename containing the full FDT contents (with + all nodes and properties) + phase (str): Phase to generate for ('tpl', 'vpl', 'spl') + outfile (str): Output filename to write the grepped FDT contents to + (with only neceesary nodes and properties) + + Returns: + CommandResult: Resulting output from the bintool, or None if the + tool is not present + """ + if phase == 'tpl': + tag = 'bootph-pre-sram' + elif phase == 'vpl': + tag = 'bootph-verify' + elif phase == 'spl': + tag = 'bootph-pre-ram' + else: + raise ValueError(f"Invalid U-Boot phase '{phase}': Use tpl/vpl/spl") + + # These args mirror those in cmd_fdtgrep in scripts/Makefile.lib + # First do the first stage + with tempfile.NamedTemporaryFile(prefix='fdtgrep.tmp', + dir=tools.get_output_dir()) as tmp: + args = [ + infile, + '-o', tmp.name, + '-b', 'bootph-all', + '-b', tag, + '-u', + '-RT', + '-n', '/chosen', + '-n', '/config', + '-O', 'dtb', + ] + self.run_cmd(*args) + args = [ + tmp.name, + '-o', outfile, + '-r', + '-O', 'dtb', + '-P', 'bootph-all', + '-P', 'bootph-pre-ram', + '-P', 'bootph-pre-sram', + '-P', 'bootph-verify', + ] + for prop_name in remove_props: + args += ['-P', prop_name] + return self.run_cmd(*args) + + def fetch(self, method): + """Fetch handler for fdtgrep + + This installs fdtgrep using the apt utility, which assumes that it is + packaged in u-boot tools, which it is not. + + Args: + method (FETCH_...): Method to use + + Returns: + True if the file was fetched and now installed, None if a method + other than FETCH_BIN was requested + + Raises: + Valuerror: Fetching could not be completed + """ + if method != bintool.FETCH_BIN: + return None + return self.apt_install('u-boot-tools') diff --git a/tools/binman/control.py b/tools/binman/control.py index 2f00279..542c2b4 100644 --- a/tools/binman/control.py +++ b/tools/binman/control.py @@ -617,6 +617,50 @@ def PrepareImagesAndDtbs(dtb_fname, select_images, update_fdt, use_expanded): dtb_item.Flush() return images +def CheckForProblems(image): + """Check for problems with image generation + + Shows warning about missing, faked or optional external blobs, as well as + missing bintools. + + Args: + image (Image): Image to process + + Returns: + bool: True if there are any problems which result in a non-functional + image + """ + missing_list = [] + image.CheckMissing(missing_list) + if missing_list: + tout.error("Image '%s' is missing external blobs and is non-functional: %s\n" % + (image.name, ' '.join([e.name for e in missing_list]))) + _ShowHelpForMissingBlobs(tout.ERROR, missing_list) + + faked_list = [] + image.CheckFakedBlobs(faked_list) + if faked_list: + tout.warning( + "Image '%s' has faked external blobs and is non-functional: %s\n" % + (image.name, ' '.join([os.path.basename(e.GetDefaultFilename()) + for e in faked_list]))) + + optional_list = [] + image.CheckOptional(optional_list) + if optional_list: + tout.warning( + "Image '%s' is missing optional external blobs but is still functional: %s\n" % + (image.name, ' '.join([e.name for e in optional_list]))) + _ShowHelpForMissingBlobs(tout.WARNING, optional_list) + + missing_bintool_list = [] + image.check_missing_bintools(missing_bintool_list) + if missing_bintool_list: + tout.warning( + "Image '%s' has missing bintools and is non-functional: %s\n" % + (image.name, ' '.join([os.path.basename(bintool.name) + for bintool in missing_bintool_list]))) + return any([missing_list, faked_list, missing_bintool_list]) def ProcessImage(image, update_fdt, write_map, get_contents=True, allow_resize=True, allow_missing=False, @@ -689,38 +733,11 @@ def ProcessImage(image, update_fdt, write_map, get_contents=True, if write_map: image.WriteMap() - missing_list = [] - image.CheckMissing(missing_list) - if missing_list: - tout.error("Image '%s' is missing external blobs and is non-functional: %s\n" % - (image.name, ' '.join([e.name for e in missing_list]))) - _ShowHelpForMissingBlobs(tout.ERROR, missing_list) - - faked_list = [] - image.CheckFakedBlobs(faked_list) - if faked_list: - tout.warning( - "Image '%s' has faked external blobs and is non-functional: %s\n" % - (image.name, ' '.join([os.path.basename(e.GetDefaultFilename()) - for e in faked_list]))) + has_problems = CheckForProblems(image) - optional_list = [] - image.CheckOptional(optional_list) - if optional_list: - tout.warning( - "Image '%s' is missing optional external blobs but is still functional: %s\n" % - (image.name, ' '.join([e.name for e in optional_list]))) - _ShowHelpForMissingBlobs(tout.WARNING, optional_list) - - missing_bintool_list = [] - image.check_missing_bintools(missing_bintool_list) - if missing_bintool_list: - tout.warning( - "Image '%s' has missing bintools and is non-functional: %s\n" % - (image.name, ' '.join([os.path.basename(bintool.name) - for bintool in missing_bintool_list]))) - return any([missing_list, faked_list, missing_bintool_list]) + image.WriteAlternates() + return has_problems def Binman(args): """The main control code for binman diff --git a/tools/binman/elf.py b/tools/binman/elf.py index 2ecc95f..a469405 100644 --- a/tools/binman/elf.py +++ b/tools/binman/elf.py @@ -275,7 +275,7 @@ def LookupAndWriteSymbols(elf_fname, entry, section, is_elf=False, return 0 base = syms.get(base_sym) if not base and not is_elf: - tout.debug('LookupAndWriteSymbols: no base') + tout.debug(f'LookupAndWriteSymbols: no base: elf_fname={elf_fname}, base_sym={base_sym}, is_elf={is_elf}') return 0 base_addr = 0 if is_elf else base.address count = 0 diff --git a/tools/binman/entries.rst b/tools/binman/entries.rst index bdda1ef..1248270 100644 --- a/tools/binman/entries.rst +++ b/tools/binman/entries.rst @@ -11,6 +11,48 @@ features to produce new behaviours. +.. _etype_alternates_fdt: + +Entry: alternates-fdt: Entry that generates alternative sections for each devicetree provided +--------------------------------------------------------------------------------------------- + +When creating an image designed to boot on multiple models, each model +requires its own devicetree. This entry deals with selecting the correct +devicetree from a directory containing them. Each one is read in turn, then +used to produce section contents which are written to a file. This results +in a number of images, one for each model. + +For example this produces images for each .dtb file in the 'dtb' directory:: + + alternates-fdt { + fdt-list-dir = "dtb"; + filename-pattern = "NAME.bin"; + fdt-phase = "tpl"; + + section { + u-boot-tpl { + }; + }; + }; + +Each output file is named based on its input file, so an input file of +`model1.dtb` results in an output file of `model1.bin` (i.e. the `NAME` in +the `filename-pattern` property is replaced with the .dtb basename). + +Note that this entry type still produces contents for the 'main' image, in +that case using the normal dtb provided to Binman, e.g. `u-boot-tpl.dtb`. +But that image is unlikely to be useful, since it relates to whatever dtb +happened to be the default when U-Boot builds +(i.e. `CONFIG_DEFAULT_DEVICE_TREE`). However, Binman ensures that the size +of each of the alternates is the same as the 'default' one, so they can in +principle be 'slotted in' to the appropriate place in the main image. + +The optional `fdt-phase` property indicates the phase to build. In this +case, it etype runs fdtgrep to obtain the devicetree subset for that phase, +respecting the `bootph-xxx` tags in the devicetree. + + + .. _etype_atf_bl31: Entry: atf-bl31: ARM Trusted Firmware (ATF) BL31 blob @@ -815,6 +857,13 @@ The top-level 'fit' node supports the following special properties: fit,fdt-list-val = "dtb1", "dtb2"; + fit,fdt-list-dir + As an alternative to fit,fdt-list the list of device tree files + can be provided as a directory. Each .dtb file in the directory is + processed, , e.g.:: + + fit,fdt-list-dir = "arch/arm/dts + Substitutions ~~~~~~~~~~~~~ @@ -890,6 +939,7 @@ You can create config nodes in a similar way:: firmware = "atf"; loadables = "uboot"; fdt = "fdt-SEQ"; + fit,compatible; // optional }; }; @@ -899,6 +949,39 @@ for each of your two files. Note that if no devicetree files are provided (with '-a of-list' as above) then no nodes will be generated. +The 'fit,compatible' property (if present) is replaced with the compatible +string from the root node of the devicetree, so that things work correctly +with FIT's configuration-matching algortihm. + +Dealing with phases +~~~~~~~~~~~~~~~~~~~ + +FIT can be used to load firmware. In this case it may be necessary to run +the devicetree for each model through fdtgrep to remove unwanted properties. +The 'fit,fdt-phase' property can be provided to indicate the phase for which +the devicetree is intended. + +For example this indicates that the FDT should be processed for VPL:: + + images { + @fdt-SEQ { + description = "fdt-NAME"; + type = "flat_dt"; + compression = "none"; + fit,fdt-phase = "vpl"; + }; + }; + +Using this mechanism, it is possible to generate a FIT which can provide VPL +images for multiple models, with TPL selecting the correct model to use. The +same approach can of course be used for SPL images. + +Note that the `of-spl-remove-props` entryarg can be used to indicate +additional properties to remove. It is often used to remove properties like +`clock-names` and `pinctrl-names` which are not needed in SPL builds. + +See :ref:`fdtgrep_filter` for more information. + Generating nodes from an ELF file (split-elf) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -2290,8 +2373,6 @@ u-boot-spl-dtb SPL can access binman symbols at runtime. See :ref:`binman_fdt`. -in the binman README for more information. - The ELF file 'spl/u-boot-spl' must also be available for this to work, since binman uses that to look up symbols to write into the SPL binary. @@ -2480,8 +2561,6 @@ u-boot-tpl-dtb TPL can access binman symbols at runtime. See :ref:`binman_fdt`. -in the binman README for more information. - The ELF file 'tpl/u-boot-tpl' must also be available for this to work, since binman uses that to look up symbols to write into the TPL binary. @@ -2571,6 +2650,9 @@ in the binman README for more information. The ELF file 'vpl/u-boot-vpl' must also be available for this to work, since binman uses that to look up symbols to write into the VPL binary. +Note that this entry is automatically replaced with u-boot-vpl-expanded +unless --no-expanded is used or the node has a 'no-expanded' property. + .. _etype_u_boot_vpl_bss_pad: @@ -2659,8 +2741,8 @@ Properties / Entry arguments: This is the U-Boot VPL binary, It does not include a device tree blob at the end of it so may not be able to work without it, assuming VPL needs -a device tree to operate on your platform. You can add a u_boot_vpl_dtb -entry after this one, or use a u_boot_vpl entry instead, which normally +a device tree to operate on your platform. You can add a u-boot-vpl-dtb +entry after this one, or use a u-boot-vpl entry instead, which normally expands to a section containing u-boot-vpl-dtb, u-boot-vpl-bss-pad and u-boot-vpl-dtb diff --git a/tools/binman/entry.py b/tools/binman/entry.py index 219d5dc..6d2f378 100644 --- a/tools/binman/entry.py +++ b/tools/binman/entry.py @@ -1199,6 +1199,9 @@ features to produce new behaviours. self.uncomp_size = len(indata) if self.comp_bintool.is_present(): data = self.comp_bintool.compress(indata) + uniq = self.GetUniqueName() + fname = tools.get_output_filename(f'comp.{uniq}') + tools.write_file(fname, data) else: self.record_missing_bintool(self.comp_bintool) data = tools.get_bytes(0, 1024) @@ -1383,3 +1386,17 @@ features to produce new behaviours. def UpdateSignatures(self, privatekey_fname, algo, input_fname): self.Raise('Updating signatures is not supported with this entry type') + + def FdtContents(self, fdt_etype): + """Get the contents of an FDT for a particular phase + + Args: + fdt_etype (str): Filename of the phase of the FDT to return, e.g. + 'u-boot-tpl-dtb' + + Returns: + tuple: + fname (str): Filename of .dtb + bytes: Contents of FDT (possibly run through fdtgrep) + """ + return self.section.FdtContents(fdt_etype) diff --git a/tools/binman/etype/alternates_fdt.py b/tools/binman/etype/alternates_fdt.py new file mode 100644 index 0000000..808f535 --- /dev/null +++ b/tools/binman/etype/alternates_fdt.py @@ -0,0 +1,132 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright 2024 Google LLC +# Written by Simon Glass <sjg@chromium.org> + +"""Entry-type module for producing multiple alternate sections""" + +import glob +import os + +from binman.entry import EntryArg +from binman.etype.section import Entry_section +from dtoc import fdt_util +from u_boot_pylib import tools + +class Entry_alternates_fdt(Entry_section): + """Entry that generates alternative sections for each devicetree provided + + When creating an image designed to boot on multiple models, each model + requires its own devicetree. This entry deals with selecting the correct + devicetree from a directory containing them. Each one is read in turn, then + used to produce section contents which are written to a file. This results + in a number of images, one for each model. + + For example this produces images for each .dtb file in the 'dtb' directory:: + + alternates-fdt { + fdt-list-dir = "dtb"; + filename-pattern = "NAME.bin"; + fdt-phase = "tpl"; + + section { + u-boot-tpl { + }; + }; + }; + + Each output file is named based on its input file, so an input file of + `model1.dtb` results in an output file of `model1.bin` (i.e. the `NAME` in + the `filename-pattern` property is replaced with the .dtb basename). + + Note that this entry type still produces contents for the 'main' image, in + that case using the normal dtb provided to Binman, e.g. `u-boot-tpl.dtb`. + But that image is unlikely to be useful, since it relates to whatever dtb + happened to be the default when U-Boot builds + (i.e. `CONFIG_DEFAULT_DEVICE_TREE`). However, Binman ensures that the size + of each of the alternates is the same as the 'default' one, so they can in + principle be 'slotted in' to the appropriate place in the main image. + + The optional `fdt-phase` property indicates the phase to build. In this + case, it etype runs fdtgrep to obtain the devicetree subset for that phase, + respecting the `bootph-xxx` tags in the devicetree. + """ + def __init__(self, section, etype, node): + super().__init__(section, etype, node) + self.fdt_list_dir = None + self.filename_pattern = None + self.required_props = ['fdt-list-dir'] + self._cur_fdt = None + self._fdt_phase = None + self.fdtgrep = None + self._fdt_dir = None + self._fdts = None + self._fname_pattern = None + self._remove_props = None + self.alternates = None + + def ReadNode(self): + """Read properties from the node""" + super().ReadNode() + self._fdt_dir = fdt_util.GetString(self._node, 'fdt-list-dir') + fname = tools.get_input_filename(self._fdt_dir) + fdts = glob.glob('*.dtb', root_dir=fname) + self._fdts = [os.path.splitext(f)[0] for f in fdts] + + self._fdt_phase = fdt_util.GetString(self._node, 'fdt-phase') + + # This is used by Image.WriteAlternates() + self.alternates = self._fdts + + self._fname_pattern = fdt_util.GetString(self._node, 'filename-pattern') + + self._remove_props = [] + props, = self.GetEntryArgsOrProps( + [EntryArg('of-spl-remove-props', str)], required=False) + if props: + self._remove_props = props.split() + + def FdtContents(self, fdt_etype): + # If there is no current FDT, just use the normal one + if not self._cur_fdt: + return self.section.FdtContents(fdt_etype) + + # Find the file to use + fname = os.path.join(self._fdt_dir, f'{self._cur_fdt}.dtb') + infile = tools.get_input_filename(fname) + + # Run fdtgrep if needed, to remove unwanted nodes and properties + if self._fdt_phase: + uniq = self.GetUniqueName() + outfile = tools.get_output_filename( + f'{uniq}.{self._cur_fdt}-{self._fdt_phase}.dtb') + self.fdtgrep.create_for_phase(infile, self._fdt_phase, outfile, + self._remove_props) + return outfile, tools.read_file(outfile) + return fname, tools.read_file(infile) + + def ProcessWithFdt(self, alt): + """Produce the contents of this entry, using a particular FDT blob + + Args: + alt (str): Name of the alternate + + Returns: + tuple: + str: Filename to use for the alternate's .bin file + bytes: Contents of this entry's section, using the selected FDT + """ + pattern = self._fname_pattern or 'NAME.bin' + fname = pattern.replace('NAME', alt) + + data = b'' + try: + self._cur_fdt = alt + self.ProcessContents() + data = self.GetPaddedData() + finally: + self._cur_fdt = None + return fname, data + + def AddBintools(self, btools): + super().AddBintools(btools) + self.fdtgrep = self.AddBintool(btools, 'fdtgrep') diff --git a/tools/binman/etype/blob_dtb.py b/tools/binman/etype/blob_dtb.py index d543de9..b234323 100644 --- a/tools/binman/etype/blob_dtb.py +++ b/tools/binman/etype/blob_dtb.py @@ -41,12 +41,12 @@ class Entry_blob_dtb(Entry_blob): def ObtainContents(self, fake_size=0): """Get the device-tree from the list held by the 'state' module""" self._filename = self.GetDefaultFilename() - self._pathname, _ = state.GetFdtContents(self.GetFdtEtype()) + self._pathname, _ = self.FdtContents(self.GetFdtEtype()) return super().ReadBlobContents() def ProcessContents(self): """Re-read the DTB contents so that we get any calculated properties""" - _, indata = state.GetFdtContents(self.GetFdtEtype()) + _, indata = self.FdtContents(self.GetFdtEtype()) if self.compress == 'zstd' and self.prepend != 'length': self.Raise('The zstd compression requires a length header') @@ -57,7 +57,9 @@ class Entry_blob_dtb(Entry_blob): def GetFdtEtype(self): """Get the entry type of this device tree - This can be 'u-boot-dtb', 'u-boot-spl-dtb' or 'u-boot-tpl-dtb' + This can be 'u-boot-dtb', 'u-boot-spl-dtb', 'u-boot-tpl-dtb' or + 'u-boot-vpl-dtb' + Returns: Entry type if any, e.g. 'u-boot-dtb' """ diff --git a/tools/binman/etype/fit.py b/tools/binman/etype/fit.py index 2c14b15..ee44e5a 100644 --- a/tools/binman/etype/fit.py +++ b/tools/binman/etype/fit.py @@ -5,7 +5,9 @@ """Entry-type module for producing a FIT""" +import glob import libfdt +import os from binman.entry import Entry, EntryArg from binman.etype.section import Entry_section @@ -87,6 +89,13 @@ class Entry_fit(Entry_section): fit,fdt-list-val = "dtb1", "dtb2"; + fit,fdt-list-dir + As an alternative to fit,fdt-list the list of device tree files + can be provided as a directory. Each .dtb file in the directory is + processed, , e.g.:: + + fit,fdt-list-dir = "arch/arm/dts + Substitutions ~~~~~~~~~~~~~ @@ -162,6 +171,7 @@ class Entry_fit(Entry_section): firmware = "atf"; loadables = "uboot"; fdt = "fdt-SEQ"; + fit,compatible; // optional }; }; @@ -171,6 +181,40 @@ class Entry_fit(Entry_section): Note that if no devicetree files are provided (with '-a of-list' as above) then no nodes will be generated. + The 'fit,compatible' property (if present) is replaced with the compatible + string from the root node of the devicetree, so that things work correctly + with FIT's configuration-matching algortihm. + + Dealing with phases + ~~~~~~~~~~~~~~~~~~~ + + FIT can be used to load firmware. In this case it may be necessary to run + the devicetree for each model through fdtgrep to remove unwanted properties. + The 'fit,fdt-phase' property can be provided to indicate the phase for which + the devicetree is intended. + + For example this indicates that the FDT should be processed for VPL:: + + images { + @fdt-SEQ { + description = "fdt-NAME"; + type = "flat_dt"; + compression = "none"; + fit,fdt-phase = "vpl"; + }; + }; + + Using this mechanism, it is possible to generate a FIT which can provide VPL + images for multiple models, with TPL selecting the correct model to use. The + same approach can of course be used for SPL images. + + Note that the `of-spl-remove-props` entryarg can be used to indicate + additional properties to remove. It is often used to remove properties like + `clock-names` and `pinctrl-names` which are not needed in SPL builds. This + value is automatically passed to binman by the U-Boot build. + + See :ref:`fdtgrep_filter` for more information. + Generating nodes from an ELF file (split-elf) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -352,9 +396,16 @@ class Entry_fit(Entry_section): self._fit = None self._fit_props = {} self._fdts = None + self._fdt_dir = None self.mkimage = None + self.fdtgrep = None self._priv_entries = {} self._loadables = [] + self._remove_props = [] + props, = self.GetEntryArgsOrProps( + [EntryArg('of-spl-remove-props', str)], required=False) + if props: + self._remove_props = props.split() def ReadNode(self): super().ReadNode() @@ -368,7 +419,14 @@ class Entry_fit(Entry_section): if fdts is not None: self._fdts = fdts.split() else: - self._fdts = fdt_util.GetStringList(self._node, 'fit,fdt-list-val') + self._fdt_dir = fdt_util.GetString(self._node, 'fit,fdt-list-dir') + if self._fdt_dir: + indir = tools.get_input_filename(self._fdt_dir) + fdts = glob.glob('*.dtb', root_dir=indir) + self._fdts = [os.path.splitext(f)[0] for f in sorted(fdts)] + else: + self._fdts = fdt_util.GetStringList(self._node, + 'fit,fdt-list-val') self._fit_default_dt = self.GetEntryArgsOrProps([EntryArg('default-dt', str)])[0] @@ -483,6 +541,19 @@ class Entry_fit(Entry_section): rel_path = node.path[len(self._node.path) + 1:] self.Raise(f"subnode '{rel_path}': {msg}") + def _run_fdtgrep(self, infile, phase, outfile): + """Run fdtgrep to create the dtb for a phase + + Args: + infile (str): Input filename containing the full FDT contents (with + all nodes and properties) + phase (str): Phase to generate for ('tpl', 'vpl', 'spl') + outfile (str): Output filename to write the grepped FDT contents to + (with only neceesary nodes and properties) + """ + return self.fdtgrep.create_for_phase(infile, phase, outfile, + self._remove_props) + def _build_input(self): """Finish the FIT by adding the 'data' properties to it @@ -584,6 +655,7 @@ class Entry_fit(Entry_section): for seq, fdt_fname in enumerate(self._fdts): node_name = node.name[1:].replace('SEQ', str(seq + 1)) fname = tools.get_input_filename(fdt_fname + '.dtb') + fdt_phase = None with fsw.add_node(node_name): for pname, prop in node.props.items(): if pname == 'fit,firmware': @@ -594,6 +666,15 @@ class Entry_fit(Entry_section): fsw.property('loadables', val.encode('utf-8')) elif pname == 'fit,operation': pass + elif pname == 'fit,compatible': + fdt_phase = fdt_util.GetString(node, pname) + data = tools.read_file(fname) + fdt = Fdt.FromData(data) + fdt.Scan() + prop = fdt.GetRoot().props['compatible'] + fsw.property('compatible', prop.bytes) + elif pname == 'fit,fdt-phase': + fdt_phase = fdt_util.GetString(node, pname) elif pname.startswith('fit,'): self._raise_subnode( node, f"Unknown directive '{pname}'") @@ -606,7 +687,14 @@ class Entry_fit(Entry_section): # Add data for 'images' nodes (but not 'config') if depth == 1 and in_images: - fsw.property('data', tools.read_file(fname)) + if fdt_phase: + phase_fname = tools.get_output_filename( + f'{fdt_fname}-{fdt_phase}.dtb') + self._run_fdtgrep(fname, fdt_phase, phase_fname) + data = tools.read_file(phase_fname) + else: + data = tools.read_file(fname) + fsw.property('data', data) for subnode in node.subnodes: with fsw.add_node(subnode.name): @@ -834,6 +922,7 @@ class Entry_fit(Entry_section): def AddBintools(self, btools): super().AddBintools(btools) self.mkimage = self.AddBintool(btools, 'mkimage') + self.fdtgrep = self.AddBintool(btools, 'fdtgrep') def CheckMissing(self, missing_list): # We must use our private entry list for this since generator nodes diff --git a/tools/binman/etype/u_boot_spl_nodtb.py b/tools/binman/etype/u_boot_spl_nodtb.py index e7ec329..0e172ae 100644 --- a/tools/binman/etype/u_boot_spl_nodtb.py +++ b/tools/binman/etype/u_boot_spl_nodtb.py @@ -23,8 +23,6 @@ class Entry_u_boot_spl_nodtb(Entry_blob): SPL can access binman symbols at runtime. See :ref:`binman_fdt`. - in the binman README for more information. - The ELF file 'spl/u-boot-spl' must also be available for this to work, since binman uses that to look up symbols to write into the SPL binary. """ diff --git a/tools/binman/etype/u_boot_tpl_nodtb.py b/tools/binman/etype/u_boot_tpl_nodtb.py index 9bb2b5d..e0c8a55 100644 --- a/tools/binman/etype/u_boot_tpl_nodtb.py +++ b/tools/binman/etype/u_boot_tpl_nodtb.py @@ -23,8 +23,6 @@ class Entry_u_boot_tpl_nodtb(Entry_blob): TPL can access binman symbols at runtime. See :ref:`binman_fdt`. - in the binman README for more information. - The ELF file 'tpl/u-boot-tpl' must also be available for this to work, since binman uses that to look up symbols to write into the TPL binary. """ diff --git a/tools/binman/etype/u_boot_vpl.py b/tools/binman/etype/u_boot_vpl.py index 31d7e83..0797831 100644 --- a/tools/binman/etype/u_boot_vpl.py +++ b/tools/binman/etype/u_boot_vpl.py @@ -27,6 +27,9 @@ class Entry_u_boot_vpl(Entry_blob): The ELF file 'vpl/u-boot-vpl' must also be available for this to work, since binman uses that to look up symbols to write into the VPL binary. + + Note that this entry is automatically replaced with u-boot-vpl-expanded + unless --no-expanded is used or the node has a 'no-expanded' property. """ def __init__(self, section, etype, node): super().__init__(section, etype, node, auto_write_symbols=True) diff --git a/tools/binman/etype/u_boot_vpl_nodtb.py b/tools/binman/etype/u_boot_vpl_nodtb.py index 64c2767..765cf53 100644 --- a/tools/binman/etype/u_boot_vpl_nodtb.py +++ b/tools/binman/etype/u_boot_vpl_nodtb.py @@ -16,8 +16,8 @@ class Entry_u_boot_vpl_nodtb(Entry_blob): This is the U-Boot VPL binary, It does not include a device tree blob at the end of it so may not be able to work without it, assuming VPL needs - a device tree to operate on your platform. You can add a u_boot_vpl_dtb - entry after this one, or use a u_boot_vpl entry instead, which normally + a device tree to operate on your platform. You can add a u-boot-vpl-dtb + entry after this one, or use a u-boot-vpl entry instead, which normally expands to a section containing u-boot-vpl-dtb, u-boot-vpl-bss-pad and u-boot-vpl-dtb diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py index e4da040..93f3d22 100644 --- a/tools/binman/ftest.py +++ b/tools/binman/ftest.py @@ -7,6 +7,7 @@ # python -m unittest func_test.TestFunctional.testHelp import collections +import glob import gzip import hashlib from optparse import OptionParser @@ -546,7 +547,7 @@ class TestFunctional(unittest.TestCase): dtb_data = self._SetupDtb(fname) # For testing purposes, make a copy of the DT for SPL and TPL. Add - # a node indicating which it is, so aid verification. + # a node indicating which it is, to aid verification. for name in ['spl', 'tpl', 'vpl']: dtb_fname = '%s/u-boot-%s.dtb' % (name, name) outfile = os.path.join(self._indir, dtb_fname) @@ -4180,8 +4181,8 @@ class TestFunctional(unittest.TestCase): data = self._DoReadFile('172_scp.dts') self.assertEqual(SCP_DATA, data[:len(SCP_DATA)]) - def testFitFdt(self): - """Test an image with an FIT with multiple FDT images""" + def CheckFitFdt(self, dts='170_fit_fdt.dts', use_fdt_list=True): + """Check an image with an FIT with multiple FDT images""" def _CheckFdt(seq, expected_data): """Check the FDT nodes @@ -4220,11 +4221,12 @@ class TestFunctional(unittest.TestCase): self.assertEqual('fdt-%d' % seq, fnode.props['fdt'].value) entry_args = { - 'of-list': 'test-fdt1 test-fdt2', 'default-dt': 'test-fdt2', } + if use_fdt_list: + entry_args['of-list'] = 'test-fdt1 test-fdt2' data = self._DoReadFileDtb( - '170_fit_fdt.dts', + dts, entry_args=entry_args, extra_indirs=[os.path.join(self._indir, TEST_FDT_SUBDIR)])[0] self.assertEqual(U_BOOT_NODTB_DATA, data[-len(U_BOOT_NODTB_DATA):]) @@ -4243,6 +4245,10 @@ class TestFunctional(unittest.TestCase): _CheckConfig(1, TEST_FDT1_DATA) _CheckConfig(2, TEST_FDT2_DATA) + def testFitFdt(self): + """Test an image with an FIT with multiple FDT images""" + self.CheckFitFdt() + def testFitFdtMissingList(self): """Test handling of a missing 'of-list' entry arg""" with self.assertRaises(ValueError) as e: @@ -7175,27 +7181,24 @@ fdt fdtmap Extract the devicetree blob from the fdtmap def testSplPubkeyDtb(self): - """Test u_boot_spl_pubkey_dtb etype""" - data = tools.read_file(self.TestFile("key.pem")) - self._MakeInputFile("key.crt", data) - self._DoReadFileRealDtb('306_spl_pubkey_dtb.dts') - image = control.images['image'] - entries = image.GetEntries() - dtb_entry = entries['u-boot-spl-pubkey-dtb'] - dtb_data = dtb_entry.GetData() - dtb = fdt.Fdt.FromData(dtb_data) - dtb.Scan() - - signature_node = dtb.GetNode('/signature') - self.assertIsNotNone(signature_node) - key_node = signature_node.FindNode("key-key") - self.assertIsNotNone(key_node) - self.assertEqual(fdt_util.GetString(key_node, "required"), - "conf") - self.assertEqual(fdt_util.GetString(key_node, "algo"), - "sha384,rsa4096") - self.assertEqual(fdt_util.GetString(key_node, "key-name-hint"), - "key") + """Test u_boot_spl_pubkey_dtb etype""" + data = tools.read_file(self.TestFile("key.pem")) + self._MakeInputFile("key.crt", data) + self._DoReadFileRealDtb('306_spl_pubkey_dtb.dts') + image = control.images['image'] + entries = image.GetEntries() + dtb_entry = entries['u-boot-spl-pubkey-dtb'] + dtb_data = dtb_entry.GetData() + dtb = fdt.Fdt.FromData(dtb_data) + dtb.Scan() + + signature_node = dtb.GetNode('/signature') + self.assertIsNotNone(signature_node) + key_node = signature_node.FindNode("key-key") + self.assertIsNotNone(key_node) + self.assertEqual(fdt_util.GetString(key_node, "required"), "conf") + self.assertEqual(fdt_util.GetString(key_node, "algo"), "sha384,rsa4096") + self.assertEqual(fdt_util.GetString(key_node, "key-name-hint"), "key") def testXilinxBootgenSigning(self): """Test xilinx-bootgen etype""" @@ -7487,6 +7490,206 @@ fdt fdtmap Extract the devicetree blob from the fdtmap err, "Image '.*' is missing external blobs and is non-functional: .*") + def SetupAlternateDts(self): + """Compile the .dts test files for alternative-fdt + + Returns: + tuple: + str: Test directory created + list of str: '.bin' files which we expect Binman to create + """ + testdir = TestFunctional._MakeInputDir('dtb') + dtb_list = [] + for fname in glob.glob(f'{self.TestFile("alt_dts")}/*.dts'): + tmp_fname = fdt_util.EnsureCompiled(fname, testdir) + base = os.path.splitext(os.path.basename(fname))[0] + dtb_list.append(base + '.bin') + shutil.move(tmp_fname, os.path.join(testdir, base + '.dtb')) + + return testdir, dtb_list + + def CheckAlternates(self, dts, phase, xpl_data): + """Run the test for the alterative-fdt etype + + Args: + dts (str): Devicetree file to process + phase (str): Phase to process ('spl', 'tpl' or 'vpl') + xpl_data (bytes): Expected data for the phase's binary + + Returns: + dict of .dtb files produced + key: str filename + value: Fdt object + """ + dtb_list = self.SetupAlternateDts()[1] + + entry_args = { + f'{phase}-dtb': '1', + f'{phase}-bss-pad': 'y', + 'of-spl-remove-props': 'prop-to-remove another-prop-to-get-rid-of', + } + data = self._DoReadFileDtb(dts, use_real_dtb=True, update_dtb=True, + use_expanded=True, entry_args=entry_args)[0] + self.assertEqual(xpl_data, data[:len(xpl_data)]) + rest = data[len(xpl_data):] + pad_len = 10 + self.assertEqual(tools.get_bytes(0, pad_len), rest[:pad_len]) + + # Check the dtb is using the test file + dtb_data = rest[pad_len:] + dtb = fdt.Fdt.FromData(dtb_data) + dtb.Scan() + fdt_size = dtb.GetFdtObj().totalsize() + self.assertEqual('model-not-set', + fdt_util.GetString(dtb.GetRoot(), 'compatible')) + + pad_len = 10 + + # Check the other output files + dtbs = {} + for fname in dtb_list: + pathname = tools.get_output_filename(fname) + self.assertTrue(os.path.exists(pathname)) + + data = tools.read_file(pathname) + self.assertEqual(xpl_data, data[:len(xpl_data)]) + rest = data[len(xpl_data):] + + self.assertEqual(tools.get_bytes(0, pad_len), rest[:pad_len]) + rest = rest[pad_len:] + + dtb = fdt.Fdt.FromData(rest) + dtb.Scan() + dtbs[fname] = dtb + + expected = 'one' if '1' in fname else 'two' + self.assertEqual(f'u-boot,model-{expected}', + fdt_util.GetString(dtb.GetRoot(), 'compatible')) + + # Make sure the FDT is the same size as the 'main' one + rest = rest[fdt_size:] + + self.assertEqual(b'', rest) + return dtbs + + def testAlternatesFdt(self): + """Test handling of alternates-fdt etype""" + self._SetupTplElf() + dtbs = self.CheckAlternates('328_alternates_fdt.dts', 'tpl', + U_BOOT_TPL_NODTB_DATA) + for dtb in dtbs.values(): + # Check for the node with the tag + node = dtb.GetNode('/node') + self.assertIsNotNone(node) + self.assertEqual(5, len(node.props.keys())) + + # Make sure the other node is still there + self.assertIsNotNone(dtb.GetNode('/node/other-node')) + + def testAlternatesFdtgrep(self): + """Test handling of alternates-fdt etype using fdtgrep""" + self._SetupTplElf() + dtbs = self.CheckAlternates('329_alternates_fdtgrep.dts', 'tpl', + U_BOOT_TPL_NODTB_DATA) + for dtb in dtbs.values(): + # Check for the node with the tag + node = dtb.GetNode('/node') + self.assertIsNotNone(node) + self.assertEqual({'some-prop', 'not-a-prop-to-remove'}, + node.props.keys()) + + # Make sure the other node is gone + self.assertIsNone(dtb.GetNode('/node/other-node')) + + def testAlternatesFdtgrepVpl(self): + """Test handling of alternates-fdt etype using fdtgrep with vpl""" + self._SetupVplElf() + dtbs = self.CheckAlternates('330_alternates_vpl.dts', 'vpl', + U_BOOT_VPL_NODTB_DATA) + + def testAlternatesFdtgrepSpl(self): + """Test handling of alternates-fdt etype using fdtgrep with spl""" + self._SetupSplElf() + dtbs = self.CheckAlternates('331_alternates_spl.dts', 'spl', + U_BOOT_SPL_NODTB_DATA) + + def testAlternatesFdtgrepInval(self): + """Test alternates-fdt etype using fdtgrep with invalid phase""" + self._SetupSplElf() + with self.assertRaises(ValueError) as e: + dtbs = self.CheckAlternates('332_alternates_inval.dts', 'spl', + U_BOOT_SPL_NODTB_DATA) + self.assertIn("Invalid U-Boot phase 'bad-phase': Use tpl/vpl/spl", + str(e.exception)) + + def testFitFdtListDir(self): + """Test an image with an FIT with FDT images using fit,fdt-list-dir""" + self.CheckFitFdt('333_fit_fdt_dir.dts', False) + + def testFitFdtCompat(self): + """Test an image with an FIT with compatible in the config nodes""" + entry_args = { + 'of-list': 'model1 model2', + 'default-dt': 'model2', + } + testdir, dtb_list = self.SetupAlternateDts() + data = self._DoReadFileDtb( + '334_fit_fdt_compat.dts', use_real_dtb=True, update_dtb=True, + entry_args=entry_args, extra_indirs=[testdir])[0] + + fit_data = data[len(U_BOOT_DATA):-len(U_BOOT_NODTB_DATA)] + + fit = fdt.Fdt.FromData(fit_data) + fit.Scan() + + cnode = fit.GetNode('/configurations') + self.assertIn('default', cnode.props) + self.assertEqual('config-2', cnode.props['default'].value) + + for seq in range(1, 2): + name = f'config-{seq}' + fnode = fit.GetNode('/configurations/%s' % name) + self.assertIsNotNone(fnode) + self.assertIn('compatible', fnode.props.keys()) + expected = 'one' if seq == 1 else 'two' + self.assertEqual(f'u-boot,model-{expected}', + fnode.props['compatible'].value) + + def testFitFdtPhase(self): + """Test an image with an FIT with fdt-phase in the fdt nodes""" + phase = 'tpl' + entry_args = { + f'{phase}-dtb': '1', + f'{phase}-bss-pad': 'y', + 'of-spl-remove-props': 'prop-to-remove another-prop-to-get-rid-of', + 'of-list': 'model1 model2', + 'default-dt': 'model2', + } + testdir, dtb_list = self.SetupAlternateDts() + data = self._DoReadFileDtb( + '335_fit_fdt_phase.dts', use_real_dtb=True, update_dtb=True, + entry_args=entry_args, extra_indirs=[testdir])[0] + fit_data = data[len(U_BOOT_DATA):-len(U_BOOT_NODTB_DATA)] + fit = fdt.Fdt.FromData(fit_data) + fit.Scan() + + # Check that each FDT has only the expected properties for the phase + for seq in range(1, 2): + fnode = fit.GetNode(f'/images/fdt-{seq}') + self.assertIsNotNone(fnode) + dtb = fdt.Fdt.FromData(fnode.props['data'].bytes) + dtb.Scan() + + # Make sure that the 'bootph-pre-sram' tag in /node protects it from + # removal + node = dtb.GetNode('/node') + self.assertIsNotNone(node) + self.assertEqual({'some-prop', 'not-a-prop-to-remove'}, + node.props.keys()) + + # Make sure the other node is gone + self.assertIsNone(dtb.GetNode('/node/other-node')) + if __name__ == "__main__": unittest.main() diff --git a/tools/binman/image.py b/tools/binman/image.py index e77b5d0..702c905 100644 --- a/tools/binman/image.py +++ b/tools/binman/image.py @@ -21,6 +21,9 @@ from dtoc import fdt_util from u_boot_pylib import tools from u_boot_pylib import tout +# This is imported if needed +state = None + class Image(section.Entry_section): """A Image, representing an output from binman @@ -75,6 +78,10 @@ class Image(section.Entry_section): def __init__(self, name, node, copy_to_orig=True, test=False, ignore_missing=False, use_expanded=False, missing_etype=False, generate=True): + # Put this here to allow entry-docs and help to work without libfdt + global state + from binman import state + super().__init__(None, 'section', node, test=test) self.copy_to_orig = copy_to_orig self.name = name @@ -186,6 +193,19 @@ class Image(section.Entry_section): os.remove(sname) os.symlink(fname, sname) + def WriteAlternates(self): + """Write out alternative devicetree blobs, each in its own file""" + alt_entry = self.FindEntryType('alternates-fdt') + if not alt_entry: + return + + for alt in alt_entry.alternates: + fname, data = alt_entry.ProcessWithFdt(alt) + pathname = tools.get_output_filename(fname) + tout.info(f"Writing alternate '{alt}' to '{pathname}'") + tools.write_file(pathname, data) + tout.info("Wrote %#x bytes" % len(data)) + def WriteMap(self): """Write a map of the image to a .map file @@ -418,3 +438,7 @@ class Image(section.Entry_section): super().AddBintools(bintools) self.bintools = bintools return bintools + + def FdtContents(self, fdt_etype): + """This base-class implementation simply calls the state function""" + return state.GetFdtContents(fdt_etype) diff --git a/tools/binman/main.py b/tools/binman/main.py index 92d2431..dc817dd 100755 --- a/tools/binman/main.py +++ b/tools/binman/main.py @@ -122,6 +122,8 @@ def RunBinman(args): ret_code = RunTests(args.debug, args.verbosity, args.processes, args.test_preserve_dirs, args.tests, args.toolpath) + if args.debug and not test_util.use_concurrent: + print('Tests can run in parallel: pip install concurrencytest') elif args.cmd == 'bintool-docs': control.write_bintool_docs(bintool.Bintool.get_tool_list()) diff --git a/tools/binman/test/328_alternates_fdt.dts b/tools/binman/test/328_alternates_fdt.dts new file mode 100644 index 0000000..c913c8e --- /dev/null +++ b/tools/binman/test/328_alternates_fdt.dts @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2024 Google LLC +// Written by Simon Glass <sjg@chromium.org> + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + compatible = "model-not-set"; + + binman { + alternates-fdt { + fdt-list-dir = "dtb"; + filename-pattern = "NAME.bin"; + + section { + u-boot-tpl { + }; + }; + }; + + blob { + filename = "blobfile"; + }; + }; +}; diff --git a/tools/binman/test/329_alternates_fdtgrep.dts b/tools/binman/test/329_alternates_fdtgrep.dts new file mode 100644 index 0000000..4169528 --- /dev/null +++ b/tools/binman/test/329_alternates_fdtgrep.dts @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2024 Google LLC +// Written by Simon Glass <sjg@chromium.org> + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + compatible = "model-not-set"; + + binman { + alternates-fdt { + fdt-list-dir = "dtb"; + filename-pattern = "NAME.bin"; + fdt-phase = "tpl"; + + section { + u-boot-tpl { + }; + }; + }; + + blob { + filename = "blobfile"; + }; + }; +}; diff --git a/tools/binman/test/330_alternates_vpl.dts b/tools/binman/test/330_alternates_vpl.dts new file mode 100644 index 0000000..5b57069 --- /dev/null +++ b/tools/binman/test/330_alternates_vpl.dts @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2024 Google LLC +// Written by Simon Glass <sjg@chromium.org> + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + compatible = "model-not-set"; + + binman { + alternates-fdt { + fdt-list-dir = "dtb"; + filename-pattern = "NAME.bin"; + fdt-phase = "vpl"; + + section { + u-boot-vpl { + }; + }; + }; + + blob { + filename = "blobfile"; + }; + }; +}; diff --git a/tools/binman/test/331_alternates_spl.dts b/tools/binman/test/331_alternates_spl.dts new file mode 100644 index 0000000..882fefc --- /dev/null +++ b/tools/binman/test/331_alternates_spl.dts @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2024 Google LLC +// Written by Simon Glass <sjg@chromium.org> + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + compatible = "model-not-set"; + + binman { + alternates-fdt { + fdt-list-dir = "dtb"; + filename-pattern = "NAME.bin"; + fdt-phase = "spl"; + + section { + u-boot-spl { + }; + }; + }; + + blob { + filename = "blobfile"; + }; + }; +}; diff --git a/tools/binman/test/332_alternates_inval.dts b/tools/binman/test/332_alternates_inval.dts new file mode 100644 index 0000000..8c145dd --- /dev/null +++ b/tools/binman/test/332_alternates_inval.dts @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2024 Google LLC +// Written by Simon Glass <sjg@chromium.org> + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + compatible = "model-not-set"; + + binman { + alternates-fdt { + fdt-list-dir = "dtb"; + filename-pattern = "NAME.bin"; + fdt-phase = "bad-phase"; + + section { + u-boot-spl { + }; + }; + }; + + blob { + filename = "blobfile"; + }; + }; +}; diff --git a/tools/binman/test/333_fit_fdt_dir.dts b/tools/binman/test/333_fit_fdt_dir.dts new file mode 100644 index 0000000..aa77845 --- /dev/null +++ b/tools/binman/test/333_fit_fdt_dir.dts @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + u-boot { + }; + fit { + description = "test-desc"; + #address-cells = <1>; + fit,fdt-list-dir = "fdts"; + + images { + kernel { + description = "Vanilla Linux kernel"; + type = "kernel"; + arch = "ppc"; + os = "linux"; + compression = "gzip"; + load = <00000000>; + entry = <00000000>; + hash-1 { + algo = "crc32"; + }; + hash-2 { + algo = "sha1"; + }; + u-boot { + }; + }; + @fdt-SEQ { + description = "fdt-NAME.dtb"; + type = "flat_dt"; + compression = "none"; + hash { + algo = "sha256"; + }; + }; + }; + + configurations { + default = "@config-DEFAULT-SEQ"; + @config-SEQ { + description = "conf-NAME.dtb"; + firmware = "uboot"; + loadables = "atf"; + fdt = "fdt-SEQ"; + }; + }; + }; + u-boot-nodtb { + }; + }; +}; diff --git a/tools/binman/test/334_fit_fdt_compat.dts b/tools/binman/test/334_fit_fdt_compat.dts new file mode 100644 index 0000000..3bf45c7 --- /dev/null +++ b/tools/binman/test/334_fit_fdt_compat.dts @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + u-boot { + }; + fit { + description = "test-desc"; + #address-cells = <1>; + fit,fdt-list = "of-list"; + + images { + kernel { + description = "Vanilla Linux kernel"; + type = "kernel"; + arch = "ppc"; + os = "linux"; + compression = "gzip"; + load = <00000000>; + entry = <00000000>; + hash-1 { + algo = "crc32"; + }; + hash-2 { + algo = "sha1"; + }; + u-boot { + }; + }; + @fdt-SEQ { + description = "fdt-NAME.dtb"; + type = "flat_dt"; + compression = "none"; + hash { + algo = "sha256"; + }; + }; + }; + + configurations { + default = "@config-DEFAULT-SEQ"; + @config-SEQ { + description = "conf-NAME.dtb"; + firmware = "uboot"; + loadables = "atf"; + fdt = "fdt-SEQ"; + fit,firmware = "vpl"; + fit,compatible; + }; + }; + }; + u-boot-nodtb { + }; + }; +}; diff --git a/tools/binman/test/335_fit_fdt_phase.dts b/tools/binman/test/335_fit_fdt_phase.dts new file mode 100644 index 0000000..f8d0740 --- /dev/null +++ b/tools/binman/test/335_fit_fdt_phase.dts @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + u-boot { + }; + fit { + description = "test-desc"; + #address-cells = <1>; + fit,fdt-list = "of-list"; + + images { + kernel { + description = "Vanilla Linux kernel"; + type = "kernel"; + arch = "ppc"; + os = "linux"; + compression = "gzip"; + load = <00000000>; + entry = <00000000>; + hash-1 { + algo = "crc32"; + }; + hash-2 { + algo = "sha1"; + }; + u-boot { + }; + }; + @fdt-SEQ { + description = "fdt-NAME.dtb"; + type = "flat_dt"; + compression = "none"; + fit,fdt-phase = "tpl"; + hash { + algo = "sha256"; + }; + }; + }; + + configurations { + default = "@config-DEFAULT-SEQ"; + @config-SEQ { + description = "conf-NAME.dtb"; + firmware = "uboot"; + loadables = "atf"; + fdt = "fdt-SEQ"; + fit,firmware = "tpl"; + fit,compatible; + }; + }; + }; + u-boot-nodtb { + }; + }; +}; diff --git a/tools/binman/test/alt_dts/model1.dts b/tools/binman/test/alt_dts/model1.dts new file mode 100644 index 0000000..01e95e8 --- /dev/null +++ b/tools/binman/test/alt_dts/model1.dts @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2024 Google LLC +// Written by Simon Glass <sjg@chromium.org> + +/dts-v1/; + +/ { + model = "Model One"; + compatible = "u-boot,model-one"; + + /* this node remains due to bootph-pre-sram tag */ + node { + some-prop; + prop-to-remove; + another-prop-to-get-rid-of; + not-a-prop-to-remove; + bootph-pre-sram; + + /* this node get removed by fdtgrep */ + other-node { + another-prop; + }; + }; +}; diff --git a/tools/binman/test/alt_dts/model2.dts b/tools/binman/test/alt_dts/model2.dts new file mode 100644 index 0000000..7829c51 --- /dev/null +++ b/tools/binman/test/alt_dts/model2.dts @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2024 Google LLC +// Written by Simon Glass <sjg@chromium.org> + +/dts-v1/; + +/ { + model = "Model Two"; + compatible = "u-boot,model-two"; + + /* this node remains due to bootph-pre-sram tag */ + node { + some-prop; + prop-to-remove; + another-prop-to-get-rid-of; + not-a-prop-to-remove; + bootph-pre-sram; + + /* this node get removed by fdtgrep */ + other-node { + another-prop; + }; + }; +}; diff --git a/tools/qconfig.py b/tools/qconfig.py index 4088079..7b868c7 100755 --- a/tools/qconfig.py +++ b/tools/qconfig.py @@ -1079,7 +1079,7 @@ def do_imply_config(config_list, add_imply, imply_flags, skip_added, for linenum in sorted(linenums, reverse=True): add_imply_rule(config[CONFIG_LEN:], fname, linenum) -def defconfig_matches(configs, re_match): +def defconfig_matches(configs, re_match, re_val): """Check if any CONFIG option matches a regex The match must be complete, i.e. from the start to end of the CONFIG option. @@ -1089,16 +1089,18 @@ def defconfig_matches(configs, re_match): key: CONFIG option value: Value of option re_match (re.Pattern): Match to check + re_val (re.Pattern): Regular expression to check against value (or None) Returns: bool: True if any CONFIG matches the regex """ - for cfg in configs: + for cfg, val in configs.items(): if re_match.fullmatch(cfg): - return True + if not re_val or re_val.fullmatch(val): + return True return False -def do_find_config(config_list): +def do_find_config(config_list, list_format): """Find boards with a given combination of CONFIGs Args: @@ -1106,6 +1108,8 @@ def do_find_config(config_list): consisting of a config option, with or without a CONFIG_ prefix. If an option is preceded by a tilde (~) then it must be false, otherwise it must be true) + list_format (bool): True to write in 'list' format, one board name per + line Returns: int: exit code (0 for success) @@ -1123,6 +1127,11 @@ def do_find_config(config_list): if cfg[0] == '~': want = False cfg = cfg[1:] + val = None + re_val = None + if '=' in cfg: + cfg, val = cfg.split('=', maxsplit=1) + re_val = re.compile(val) # Search everything that is still in the running. If it has a config # that we want, or doesn't have one that we don't, add it into the @@ -1131,11 +1140,13 @@ def do_find_config(config_list): out = set() re_match = re.compile(cfg) for defc in in_list: - has_cfg = defconfig_matches(config_db[defc], re_match) + has_cfg = defconfig_matches(config_db[defc], re_match, re_val) if has_cfg == want: out.add(defc) - print(f'{len(out)} matches') - print(' '.join(item.split('_defconfig')[0] for item in out)) + if not list_format: + print(f'{len(out)} matches') + sep = '\n' if list_format else ' ' + print(sep.join(item.split('_defconfig')[0] for item in sorted(list(out)))) return 0 @@ -1528,6 +1539,8 @@ doc/develop/moveconfig.rst for documentation.''' help='Find boards with a given config combination') parser.add_argument('-i', '--imply', action='store_true', default=False, help='find options which imply others') + parser.add_argument('-l', '--list', action='store_true', default=False, + help='Show a sorted list of board names, one per line') parser.add_argument('-I', '--imply-flags', type=str, default='', help="control the -i option ('help' for help") parser.add_argument('-j', '--jobs', type=int, default=cpu_count, @@ -1681,7 +1694,7 @@ def main(): sys.exit(1) return 0 if args.find: - return do_find_config(args.configs) + return do_find_config(args.configs, args.list) config_db, progress = move_config(args) |