diff options
author | Tom Rini <trini@konsulko.com> | 2024-10-07 09:06:49 -0600 |
---|---|---|
committer | Tom Rini <trini@konsulko.com> | 2024-10-07 09:06:49 -0600 |
commit | c530f6079c70219e01022142b404bf8a6b572366 (patch) | |
tree | 7696586afaf4c0ae8a94ef811abd7d4a2276eb64 /tools | |
parent | f919c3a889f0ec7d63a48b5d0ed064386b0980bd (diff) | |
parent | 28dc47038edc4e93f32d75a357131bcf01a18d85 (diff) | |
download | u-boot-c530f6079c70219e01022142b404bf8a6b572366.tar.gz |
Merge branch 'next'
Diffstat (limited to 'tools')
32 files changed, 800 insertions, 185 deletions
diff --git a/tools/Makefile b/tools/Makefile index 6a4280e3668..ee08a9675df 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -246,12 +246,12 @@ HOSTCFLAGS_asn1_compiler.o = -idirafter $(srctree)/include HOSTCFLAGS_mkeficapsule.o += \ $(shell pkg-config --cflags gnutls 2> /dev/null || echo "") -HOSTCFLAGS_mkeficapsule.o += \ - $(shell pkg-config --cflags uuid 2> /dev/null || echo "") HOSTLDLIBS_mkeficapsule += \ $(shell pkg-config --libs gnutls 2> /dev/null || echo "-lgnutls") -HOSTLDLIBS_mkeficapsule += \ - $(shell pkg-config --libs uuid 2> /dev/null || echo "-luuid") +mkeficapsule-objs := generated/lib/uuid.o \ + generated/lib/sha1.o \ + $(LIBFDT_OBJS) \ + mkeficapsule.o hostprogs-$(CONFIG_TOOLS_MKEFICAPSULE) += mkeficapsule mkfwumdata-objs := mkfwumdata.o generated/lib/crc32.o diff --git a/tools/binman/binman.rst b/tools/binman/binman.rst index 0cafc36bdcb..f9a3a42183b 100644 --- a/tools/binman/binman.rst +++ b/tools/binman/binman.rst @@ -494,12 +494,18 @@ point into the image. For example, say SPL is at the start of the image and linked to start at address 80108000. If U-Boot's image-pos is 0x8000 then binman will write an image-pos for U-Boot of 80110000 into the SPL binary, since it assumes the image is loaded -to 80108000, with SPL at 80108000 and U-Boot at 80110000. +to 80108000, with SPL at 80108000 and U-Boot at 80110000. In other words, the +positions are calculated relative to the start address of the image to which +they are being written. For x86 devices (with the end-at-4gb property) this base address is not added since it is assumed that images are XIP and the offsets already include the address. +For non-x86 cases where the symbol is used as a flash offset, the symbols-base +property can be set to that offset (e.g. 0), so that the unadjusted image-pos +is written into the image. + While U-Boot's symbol updating is handled automatically by the u-boot-spl entry type (and others), it is possible to use this feature with any blob. To do this, add a `write-symbols` (boolean) property to the node, set the ELF @@ -741,6 +747,17 @@ insert-template: properties are brought into the target node. See Templates_ below for more information. +symbols-base: + When writing symbols into a binary, the value of that symbol is assumed to + be relative to the base address of the binary. This allow the binary to be + loaded in memory at its base address, so that symbols point into the binary + correctly. In some cases the binary is in fact not yet in memory, but must + be read from storage. In this case there is no base address for the symbols. + This property can be set to 0 to indicate this. Other values for + symbols-base are allowed, but care must be taken that the code which uses + the symbol is aware of the base being used. If omitted, the binary's base + address is used. + The attributes supported for images and sections are described below. Several are similar to those for entries. diff --git a/tools/binman/btool/fdtgrep.py b/tools/binman/btool/fdtgrep.py index da1f8c7bf4e..446b2f4144b 100644 --- a/tools/binman/btool/fdtgrep.py +++ b/tools/binman/btool/fdtgrep.py @@ -74,8 +74,7 @@ class Bintoolfdtgrep(bintool.Bintool): (with only neceesary nodes and properties) Returns: - CommandResult: Resulting output from the bintool, or None if the - tool is not present + str or bytes: Resulting stdout from the bintool """ if phase == 'tpl': tag = 'bootph-pre-sram' diff --git a/tools/binman/elf.py b/tools/binman/elf.py index a4694056391..c75f4478813 100644 --- a/tools/binman/elf.py +++ b/tools/binman/elf.py @@ -234,7 +234,7 @@ def GetSymbolOffset(elf_fname, sym_name, base_sym=None): return val - base def LookupAndWriteSymbols(elf_fname, entry, section, is_elf=False, - base_sym=None): + base_sym=None, base_addr=None): """Replace all symbols in an entry with their correct values The entry contents is updated so that values for referenced symbols will be @@ -247,7 +247,10 @@ def LookupAndWriteSymbols(elf_fname, entry, section, is_elf=False, entry entry: Entry to process section: Section which can be used to lookup symbol values - base_sym: Base symbol marking the start of the image + base_sym: Base symbol marking the start of the image (__image_copy_start + by default) + base_addr (int): Base address to use for the entry being written. If + None then the value of base_sym is used Returns: int: Number of symbols written @@ -277,7 +280,8 @@ def LookupAndWriteSymbols(elf_fname, entry, section, is_elf=False, if not base and not is_elf: 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 + if base_addr is None: + base_addr = 0 if is_elf else base.address count = 0 for name, sym in syms.items(): if name.startswith('_binman'): @@ -301,8 +305,8 @@ def LookupAndWriteSymbols(elf_fname, entry, section, is_elf=False, value = BINMAN_SYM_MAGIC_VALUE else: # Look up the symbol in our entry tables. - value = section.GetImage().LookupImageSymbol(name, sym.weak, - msg, base_addr) + value = section.GetImage().GetImageSymbolValue(name, sym.weak, + msg, base_addr) if value is None: value = -1 pack_string = pack_string.lower() diff --git a/tools/binman/elf_test.py b/tools/binman/elf_test.py index b64134123c1..2f22639dffc 100644 --- a/tools/binman/elf_test.py +++ b/tools/binman/elf_test.py @@ -37,7 +37,7 @@ class FakeSection: """A fake Section object, used for testing This has the minimum feature set needed to support testing elf functions. - A LookupSymbol() function is provided which returns a fake value for amu + A GetSymbolValue() function is provided which returns a fake value for any symbol requested. """ def __init__(self, sym_value=1): @@ -46,7 +46,7 @@ class FakeSection: def GetPath(self): return 'section_path' - def LookupImageSymbol(self, name, weak, msg, base_addr): + def GetImageSymbolValue(self, name, weak, msg, base_addr): """Fake implementation which returns the same value for all symbols""" return self.sym_value diff --git a/tools/binman/entry.py b/tools/binman/entry.py index 6d2f3789940..68f8d62bba9 100644 --- a/tools/binman/entry.py +++ b/tools/binman/entry.py @@ -108,6 +108,9 @@ class Entry(object): not need to be done again. This is only used with 'binman replace', to stop sections from being rebuilt if their entries have not been replaced + symbols_base (int): Use this value as the assumed load address of the + target entry, when calculating the symbol value. If None, this is + 0 for blobs and the image-start address for ELF files """ fake_dir = None @@ -159,6 +162,7 @@ class Entry(object): self.preserve = False self.build_done = False self.no_write_symbols = False + self.symbols_base = None @staticmethod def FindEntryClass(etype, expanded): @@ -324,6 +328,7 @@ class Entry(object): self.preserve = fdt_util.GetBool(self._node, 'preserve') self.no_write_symbols = fdt_util.GetBool(self._node, 'no-write-symbols') + self.symbols_base = fdt_util.GetInt(self._node, 'symbols-base') def GetDefaultFilename(self): return None @@ -576,8 +581,16 @@ class Entry(object): def GetEntryArgsOrProps(self, props, required=False): """Return the values of a set of properties + Looks up the named entryargs and returns the value for each. If any + required ones are missing, the error is reported to the user. + Args: - props: List of EntryArg objects + props (list of EntryArg): List of entry arguments to look up + required (bool): True if these entry arguments are required + + Returns: + list of values: one for each item in props, the type is determined + by the EntryArg's 'datatype' property (str or int) Raises: ValueError if a property is not found @@ -698,14 +711,22 @@ class Entry(object): def WriteSymbols(self, section): """Write symbol values into binary files for access at run time + As a special case, if symbols_base is not specified and this is an + end-at-4gb image, a symbols_base of 0 is used + Args: section: Section containing the entry """ if self.auto_write_symbols and not self.no_write_symbols: # Check if we are writing symbols into an ELF file is_elf = self.GetDefaultFilename() == self.elf_fname + + symbols_base = self.symbols_base + if symbols_base is None and self.GetImage()._end_4gb: + symbols_base = 0 + elf.LookupAndWriteSymbols(self.elf_fname, self, section.GetImage(), - is_elf, self.elf_base_sym) + is_elf, self.elf_base_sym, symbols_base) def CheckEntries(self): """Check that the entry offsets are correct diff --git a/tools/binman/etype/atf_fip.py b/tools/binman/etype/atf_fip.py index 3da0dfcfc12..636e073afc8 100644 --- a/tools/binman/etype/atf_fip.py +++ b/tools/binman/etype/atf_fip.py @@ -248,7 +248,7 @@ class Entry_atf_fip(Entry_section): fent = entry._fip_entry entry.size = fent.size entry.offset = fent.offset - entry.image_pos = self.image_pos + entry.offset + entry.SetImagePos(image_pos + self.offset) def ReadChildData(self, child, decomp=True, alt_format=None): if not self.reader: diff --git a/tools/binman/etype/blob_phase.py b/tools/binman/etype/blob_phase.py index 951d9934050..09bb89b3b78 100644 --- a/tools/binman/etype/blob_phase.py +++ b/tools/binman/etype/blob_phase.py @@ -57,3 +57,8 @@ class Entry_blob_phase(Entry_section): if self.no_write_symbols: for entry in self._entries.values(): entry.no_write_symbols = True + + # Propagate the symbols-base property + if self.symbols_base is not None: + for entry in self._entries.values(): + entry.symbols_base = self.symbols_base diff --git a/tools/binman/etype/cbfs.py b/tools/binman/etype/cbfs.py index 575aa624f6c..124fa1e4ffc 100644 --- a/tools/binman/etype/cbfs.py +++ b/tools/binman/etype/cbfs.py @@ -245,7 +245,7 @@ class Entry_cbfs(Entry): cfile = entry._cbfs_file entry.size = cfile.data_len entry.offset = cfile.calced_cbfs_offset - entry.image_pos = self.image_pos + entry.offset + entry.SetImagePos(image_pos + self.offset) if entry._cbfs_compress: entry.uncomp_size = cfile.memlen diff --git a/tools/binman/etype/efi_capsule.py b/tools/binman/etype/efi_capsule.py index 5941545d0b2..9f06cc88e6e 100644 --- a/tools/binman/etype/efi_capsule.py +++ b/tools/binman/etype/efi_capsule.py @@ -24,7 +24,7 @@ def get_binman_test_guid(type_str): The actual GUID value (str) """ TYPE_TO_GUID = { - 'binman-test' : '09d7cf52-0720-4710-91d1-08469b7fe9c8' + 'binman-test' : '985f2937-7c2e-5e9a-8a5e-8e063312964b' } return TYPE_TO_GUID[type_str] @@ -151,6 +151,8 @@ class Entry_efi_capsule(Entry_section): return tools.read_file(capsule_fname) else: # Bintool is missing; just use the input data as the output + if not self.GetAllowMissing(): + self.Raise("Missing tool: 'mkeficapsule'") self.record_missing_bintool(self.mkeficapsule) return data diff --git a/tools/binman/etype/fit.py b/tools/binman/etype/fit.py index ee44e5a1cd6..0abe1c78c43 100644 --- a/tools/binman/etype/fit.py +++ b/tools/binman/etype/fit.py @@ -6,9 +6,10 @@ """Entry-type module for producing a FIT""" import glob -import libfdt import os +import libfdt + from binman.entry import Entry, EntryArg from binman.etype.section import Entry_section from binman import elf @@ -23,6 +24,7 @@ OPERATIONS = { 'split-elf': OP_SPLIT_ELF, } +# pylint: disable=invalid-name class Entry_fit(Entry_section): """Flat Image Tree (FIT) @@ -94,7 +96,10 @@ class Entry_fit(Entry_section): can be provided as a directory. Each .dtb file in the directory is processed, , e.g.:: - fit,fdt-list-dir = "arch/arm/dts + fit,fdt-list-dir = "arch/arm/dts"; + + In this case the input directories are ignored and all devicetree + files must be in that directory. Substitutions ~~~~~~~~~~~~~ @@ -381,31 +386,46 @@ class Entry_fit(Entry_section): def __init__(self, section, etype, node): """ Members: - _fit: FIT file being built - _entries: dict from Entry_section: + _fit (str): FIT file being built + _fit_props (list of str): 'fit,...' properties found in the + top-level node + _fdts (list of str): Filenames of .dtb files to process + _fdt_dir (str): Directory to scan to find .dtb files, or None + _fit_list_prop (str): Name of the EntryArg containing a list of .dtb + files + _fit_default_dt (str): Name of the EntryArg containing the default + .dtb file + _entries (dict of entries): from Entry_section: key: relative path to entry Node (from the base of the FIT) value: Entry_section object comprising the contents of this node - _priv_entries: Internal copy of _entries which includes 'generator' - entries which are used to create the FIT, but should not be - processed as real entries. This is set up once we have the - entries - _loadables: List of generated split-elf nodes, each a node name + _priv_entries (dict of entries): Internal copy of _entries which + includes 'generator' entries which are used to create the FIT, + but should not be processed as real entries. This is set up once + we have the entries + _loadables (list of str): List of generated split-elf nodes, each + a node name + _remove_props (list of str): Value of of-spl-remove-props EntryArg, + the list of properties to remove with fdtgrep + mkimage (Bintool): mkimage tool + fdtgrep (Bintool): fdtgrep tool """ super().__init__(section, etype, node) self._fit = None self._fit_props = {} self._fdts = None self._fdt_dir = None - self.mkimage = None - self.fdtgrep = None + self._fit_list_prop = None + self._fit_default_dt = None self._priv_entries = {} self._loadables = [] self._remove_props = [] - props, = self.GetEntryArgsOrProps( - [EntryArg('of-spl-remove-props', str)], required=False) + props = self.GetEntryArgsOrProps( + [EntryArg('of-spl-remove-props', str)], required=False)[0] if props: self._remove_props = props.split() + self.mkimage = None + self.fdtgrep = None def ReadNode(self): super().ReadNode() @@ -414,8 +434,8 @@ class Entry_fit(Entry_section): self._fit_props[pname] = prop self._fit_list_prop = self._fit_props.get('fit,fdt-list') if self._fit_list_prop: - fdts, = self.GetEntryArgsOrProps( - [EntryArg(self._fit_list_prop.value, str)]) + fdts = self.GetEntryArgsOrProps( + [EntryArg(self._fit_list_prop.value, str)])[0] if fdts is not None: self._fdts = fdts.split() else: @@ -431,7 +451,7 @@ class Entry_fit(Entry_section): self._fit_default_dt = self.GetEntryArgsOrProps([EntryArg('default-dt', str)])[0] - def _get_operation(self, base_node, node): + def _get_operation(self, node): """Get the operation referenced by a subnode Args: @@ -550,6 +570,9 @@ class Entry_fit(Entry_section): 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: + str or bytes: Resulting stdout from fdtgrep """ return self.fdtgrep.create_for_phase(infile, phase, outfile, self._remove_props) @@ -557,9 +580,6 @@ class Entry_fit(Entry_section): def _build_input(self): """Finish the FIT by adding the 'data' properties to it - Arguments: - fdt: FIT to update - Returns: bytes: New fdt contents """ @@ -580,13 +600,17 @@ class Entry_fit(Entry_section): if val.startswith('@'): if not self._fdts: return - if not self._fit_default_dt: + default_dt = self._fit_default_dt + if not default_dt: self.Raise("Generated 'default' node requires default-dt entry argument") - if self._fit_default_dt not in self._fdts: - self.Raise( - f"default-dt entry argument '{self._fit_default_dt}' " - f"not found in fdt list: {', '.join(self._fdts)}") - seq = self._fdts.index(self._fit_default_dt) + if default_dt not in self._fdts: + if self._fdt_dir: + default_dt = os.path.basename(default_dt) + if default_dt not in self._fdts: + self.Raise( + f"default-dt entry argument '{self._fit_default_dt}' " + f"not found in fdt list: {', '.join(self._fdts)}") + seq = self._fdts.index(default_dt) val = val[1:].replace('DEFAULT-SEQ', str(seq + 1)) fsw.property_string(pname, val) return @@ -634,7 +658,7 @@ class Entry_fit(Entry_section): result.append(name) return firmware, result - def _gen_fdt_nodes(base_node, node, depth, in_images): + def _gen_fdt_nodes(node, depth, in_images): """Generate FDT nodes This creates one node for each member of self._fdts using the @@ -654,7 +678,10 @@ class Entry_fit(Entry_section): # Generate nodes for each FDT 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') + if self._fdt_dir: + fname = os.path.join(self._fdt_dir, fdt_fname + '.dtb') + else: + fname = tools.get_input_filename(fdt_fname + '.dtb') fdt_phase = None with fsw.add_node(node_name): for pname, prop in node.props.items(): @@ -688,8 +715,9 @@ class Entry_fit(Entry_section): # Add data for 'images' nodes (but not 'config') if depth == 1 and in_images: if fdt_phase: + leaf = os.path.basename(fdt_fname) phase_fname = tools.get_output_filename( - f'{fdt_fname}-{fdt_phase}.dtb') + f'{leaf}-{fdt_phase}.dtb') self._run_fdtgrep(fname, fdt_phase, phase_fname) data = tools.read_file(phase_fname) else: @@ -707,11 +735,10 @@ class Entry_fit(Entry_section): else: self.Raise("Generator node requires 'fit,fdt-list' property") - def _gen_split_elf(base_node, node, depth, segments, entry_addr): + def _gen_split_elf(node, depth, segments, entry_addr): """Add nodes for the ELF file, one per group of contiguous segments Args: - base_node (Node): Template node from the binman definition node (Node): Node to replace (in the FIT being built) depth: Current node depth (0 is the base 'fit' node) segments (list): list of segments, each: @@ -742,7 +769,7 @@ class Entry_fit(Entry_section): with fsw.add_node(subnode.name): _add_node(node, depth + 1, subnode) - def _gen_node(base_node, node, depth, in_images, entry): + def _gen_node(node, depth, in_images, entry): """Generate nodes from a template This creates one or more nodes depending on the fit,operation being @@ -758,8 +785,6 @@ class Entry_fit(Entry_section): If the file is missing, nothing is generated. Args: - base_node (Node): Base Node of the FIT (with 'description' - property) node (Node): Generator node to process depth (int): Current node depth (0 is the base 'fit' node) in_images (bool): True if this is inside the 'images' node, so @@ -767,13 +792,12 @@ class Entry_fit(Entry_section): entry (entry_Section): Entry for the section containing the contents of this node """ - oper = self._get_operation(base_node, node) + oper = self._get_operation(node) if oper == OP_GEN_FDT_NODES: - _gen_fdt_nodes(base_node, node, depth, in_images) + _gen_fdt_nodes(node, depth, in_images) elif oper == OP_SPLIT_ELF: # Entry_section.ObtainContents() either returns True or # raises an exception. - data = None missing_opt_list = [] entry.ObtainContents() entry.Pack(0) @@ -795,7 +819,7 @@ class Entry_fit(Entry_section): self._raise_subnode( node, f'Failed to read ELF file: {str(exc)}') - _gen_split_elf(base_node, node, depth, segments, entry_addr) + _gen_split_elf(node, depth, segments, entry_addr) def _add_node(base_node, depth, node): """Add nodes to the output FIT @@ -826,7 +850,6 @@ class Entry_fit(Entry_section): fsw.property('data', bytes(data)) for subnode in node.subnodes: - subnode_path = f'{rel_path}/{subnode.name}' if has_images and not self.IsSpecialSubnode(subnode): # This subnode is a content node not meant to appear in # the FIT (e.g. "/images/kernel/u-boot"), so don't call @@ -834,7 +857,7 @@ class Entry_fit(Entry_section): pass elif self.GetImage().generate and subnode.name.startswith('@'): entry = self._priv_entries.get(subnode.name) - _gen_node(base_node, subnode, depth, in_images, entry) + _gen_node(subnode, depth, in_images, entry) # This is a generator (template) entry, so remove it from # the list of entries used by PackEntries(), etc. Otherwise # it will appear in the binman output @@ -876,7 +899,10 @@ class Entry_fit(Entry_section): """ if self.build_done: return - super().SetImagePos(image_pos) + + # Skip the section processing, since we do that below. Just call the + # entry method + Entry.SetImagePos(self, image_pos) # If mkimage is missing we'll have empty data, # which will cause a FDT_ERR_BADMAGIC error @@ -886,7 +912,7 @@ class Entry_fit(Entry_section): fdt = Fdt.FromData(self.GetData()) fdt.Scan() - for image_name, section in self._entries.items(): + for image_name, entry in self._entries.items(): path = f"/images/{image_name}" node = fdt.GetNode(path) @@ -914,10 +940,12 @@ class Entry_fit(Entry_section): # This should never happen else: # pragma: no cover + offset = None + size = None self.Raise(f'{path}: missing data properties') - section.SetOffsetSize(offset, size) - section.SetImagePos(self.image_pos) + entry.SetOffsetSize(offset, size) + entry.SetImagePos(image_pos + self.offset) def AddBintools(self, btools): super().AddBintools(btools) @@ -947,7 +975,7 @@ class Entry_fit(Entry_section): if input_fname: fname = input_fname else: - fname = tools.get_output_filename('%s.fit' % uniq) + fname = tools.get_output_filename(f'{uniq}.fit') tools.write_file(fname, self.GetData()) args.append(fname) diff --git a/tools/binman/etype/nxp_imx8mimage.py b/tools/binman/etype/nxp_imx8mimage.py index 3585120b79b..8ad177b3b65 100644 --- a/tools/binman/etype/nxp_imx8mimage.py +++ b/tools/binman/etype/nxp_imx8mimage.py @@ -27,7 +27,8 @@ class Entry_nxp_imx8mimage(Entry_mkimage): def __init__(self, section, etype, node): super().__init__(section, etype, node) - self.required_props = ['nxp,boot-from', 'nxp,rom-version', 'nxp,loader-address'] + self.required_props = ['nxp,boot-from', 'nxp,rom-version', + 'nxp,loader-address'] def ReadNode(self): super().ReadNode() diff --git a/tools/binman/etype/section.py b/tools/binman/etype/section.py index 30c1041c7e8..f4f48c00e87 100644 --- a/tools/binman/etype/section.py +++ b/tools/binman/etype/section.py @@ -563,13 +563,13 @@ class Entry_section(Entry): return entry.GetData(required) def LookupEntry(self, entries, sym_name, msg): - """Look up the entry for an ENF symbol + """Look up the entry for a binman symbol Args: entries (dict): entries to search: key: entry name value: Entry object - sym_name: Symbol name in the ELF file to look up in the format + sym_name: Symbol name to look up in the format _binman_<entry>_prop_<property> where <entry> is the name of the entry and <property> is the property to find (e.g. _binman_u_boot_prop_offset). As a special case, you can append @@ -606,11 +606,10 @@ class Entry_section(Entry): entry = entries[name] return entry, entry_name, prop_name - def LookupSymbol(self, sym_name, optional, msg, base_addr, entries=None): - """Look up a symbol in an ELF file + def GetSymbolValue(self, sym_name, optional, msg, base_addr, entries=None): + """Get the value of a Binman symbol - Looks up a symbol in an ELF file. Only entry types which come from an - ELF image can be used by this function. + Look up a Binman symbol and obtain its value. At present the only entry properties supported are: offset @@ -618,7 +617,7 @@ class Entry_section(Entry): size Args: - sym_name: Symbol name in the ELF file to look up in the format + sym_name: Symbol name to look up in the format _binman_<entry>_prop_<property> where <entry> is the name of the entry and <property> is the property to find (e.g. _binman_u_boot_prop_offset). As a special case, you can append @@ -628,12 +627,10 @@ class Entry_section(Entry): optional: True if the symbol is optional. If False this function will raise if the symbol is not found msg: Message to display if an error occurs - base_addr: Base address of image. This is added to the returned - image_pos in most cases so that the returned position indicates - where the targetted entry/binary has actually been loaded. But - if end-at-4gb is used, this is not done, since the binary is - already assumed to be linked to the ROM position and using - execute-in-place (XIP). + base_addr (int): Base address of image. This is added to the + returned value of image-pos so that the returned position + indicates where the targeted entry/binary has actually been + loaded Returns: Value that should be assigned to that symbol, or None if it was @@ -656,10 +653,10 @@ class Entry_section(Entry): if prop_name == 'offset': return entry.offset elif prop_name == 'image_pos': - value = entry.image_pos - if not self.GetImage()._end_4gb: - value += base_addr - return value + if not entry.image_pos: + tout.info(f'Symbol-writing: no value for {entry._node.path}') + return None + return base_addr + entry.image_pos if prop_name == 'size': return entry.size else: diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py index 93f3d22cf57..e3f231e4bcc 100644 --- a/tools/binman/ftest.py +++ b/tools/binman/ftest.py @@ -125,7 +125,7 @@ TEE_ADDR = 0x5678 # Firmware Management Protocol(FMP) GUID FW_MGMT_GUID = '6dcbd5ed-e82d-4c44-bda1-7194199ad92a' # Image GUID specified in the DTS -CAPSULE_IMAGE_GUID = '09d7cf52-0720-4710-91d1-08469b7fe9c8' +CAPSULE_IMAGE_GUID = '985F2937-7C2E-5E9A-8A5E-8E063312964B' # Windows cert GUID WIN_CERT_TYPE_EFI_GUID = '4aafd29d-68df-49ee-8aa9-347d375665a7' # Empty capsule GUIDs @@ -403,8 +403,10 @@ class TestFunctional(unittest.TestCase): test_section_timeout: True to force the first time to timeout, as used in testThreadTimeout() update_fdt_in_elf: Value to pass with --update-fdt-in-elf=xxx - force_missing_tools (str): comma-separated list of bintools to + force_missing_bintools (str): comma-separated list of bintools to regard as missing + ignore_missing (bool): True to return success even if there are + missing blobs or bintools output_dir: Specific output directory to use for image using -O Returns: @@ -503,8 +505,9 @@ class TestFunctional(unittest.TestCase): return dtb.GetContents() def _DoReadFileDtb(self, fname, use_real_dtb=False, use_expanded=False, - map=False, update_dtb=False, entry_args=None, - reset_dtbs=True, extra_indirs=None, threads=None): + verbosity=None, map=False, update_dtb=False, + entry_args=None, reset_dtbs=True, extra_indirs=None, + threads=None): """Run binman and return the resulting image This runs binman with a given test file and then reads the resulting @@ -521,6 +524,7 @@ class TestFunctional(unittest.TestCase): But in some test we need the real contents. use_expanded: True to use expanded entries where available, e.g. 'u-boot-expanded' instead of 'u-boot' + verbosity: Verbosity level to use (0-3, None=don't set it) map: True to output map files for the images update_dtb: Update the offset and size of each entry in the device tree before packing it into the image @@ -557,7 +561,8 @@ class TestFunctional(unittest.TestCase): try: retcode = self._DoTestFile(fname, map=map, update_dtb=update_dtb, entry_args=entry_args, use_real_dtb=use_real_dtb, - use_expanded=use_expanded, extra_indirs=extra_indirs, + use_expanded=use_expanded, verbosity=verbosity, + extra_indirs=extra_indirs, threads=threads) self.assertEqual(0, retcode) out_dtb_fname = tools.get_output_filename('u-boot.dtb.out') @@ -1498,18 +1503,22 @@ class TestFunctional(unittest.TestCase): self.assertEqual(U_BOOT_SPL_NODTB_DATA, data[:len(U_BOOT_SPL_NODTB_DATA)]) def checkSymbols(self, dts, base_data, u_boot_offset, entry_args=None, - use_expanded=False, no_write_symbols=False): + use_expanded=False, no_write_symbols=False, + symbols_base=None): """Check the image contains the expected symbol values Args: dts: Device tree file to use for test base_data: Data before and after 'u-boot' section - u_boot_offset: Offset of 'u-boot' section in image + u_boot_offset (int): Offset of 'u-boot' section in image, or None if + the offset not available due to it being in a compressed section entry_args: Dict of entry args to supply to binman key: arg name value: value of that arg use_expanded: True to use expanded entries where available, e.g. 'u-boot-expanded' instead of 'u-boot' + symbols_base (int): Value to expect for symbols-base in u-boot-spl, + None if none """ elf_fname = self.ElfTestFile('u_boot_binman_syms') syms = elf.GetSymbols(elf_fname, ['binman', 'image']) @@ -1520,22 +1529,64 @@ class TestFunctional(unittest.TestCase): self._SetupSplElf('u_boot_binman_syms') data = self._DoReadFileDtb(dts, entry_args=entry_args, - use_expanded=use_expanded)[0] + use_expanded=use_expanded, + verbosity=None if u_boot_offset else 3)[0] + + # The lz4-compressed version of the U-Boot data is 19 bytes long + comp_uboot_len = 19 + # The image should contain the symbols from u_boot_binman_syms.c # Note that image_pos is adjusted by the base address of the image, # which is 0x10 in our test image - sym_values = struct.pack('<LLQLL', elf.BINMAN_SYM_MAGIC_VALUE, - 0x00, u_boot_offset + len(U_BOOT_DATA), - 0x10 + u_boot_offset, 0x04) + # If u_boot_offset is None, Binman should write -1U into the image + vals2 = (elf.BINMAN_SYM_MAGIC_VALUE, 0x00, + u_boot_offset + len(U_BOOT_DATA) if u_boot_offset else + len(U_BOOT_SPL_DATA) + 1 + comp_uboot_len, + 0x10 + u_boot_offset if u_boot_offset else 0xffffffff, 0x04) + + # u-boot-spl has a symbols-base property, so take that into account if + # required. The caller must supply the value + vals = list(vals2) + if symbols_base is not None: + vals[3] = symbols_base + u_boot_offset + vals = tuple(vals) + + sym_values = struct.pack('<LLQLL', *vals) + sym_values2 = struct.pack('<LLQLL', *vals2) if no_write_symbols: - expected = (base_data + - tools.get_bytes(0xff, 0x38 - len(base_data)) + - U_BOOT_DATA + base_data) + self.assertEqual( + base_data + + tools.get_bytes(0xff, 0x38 - len(base_data)) + + U_BOOT_DATA + base_data, data) else: - expected = (sym_values + base_data[24:] + - tools.get_bytes(0xff, 1) + U_BOOT_DATA + sym_values + - base_data[24:]) - self.assertEqual(expected, data) + got_vals = struct.unpack('<LLQLL', data[:24]) + + # For debugging: + #print('expect:', list(f'{v:x}' for v in vals)) + #print(' got:', list(f'{v:x}' for v in got_vals)) + + self.assertEqual(vals, got_vals) + self.assertEqual(sym_values, data[:24]) + + blen = len(base_data) + self.assertEqual(base_data[24:], data[24:blen]) + self.assertEqual(0xff, data[blen]) + + if u_boot_offset: + ofs = blen + 1 + len(U_BOOT_DATA) + self.assertEqual(U_BOOT_DATA, data[blen + 1:ofs]) + else: + ofs = blen + 1 + comp_uboot_len + + self.assertEqual(sym_values2, data[ofs:ofs + 24]) + self.assertEqual(base_data[24:], data[ofs + 24:]) + + # Just repeating the above asserts all at once, for clarity + if u_boot_offset: + expected = (sym_values + base_data[24:] + + tools.get_bytes(0xff, 1) + U_BOOT_DATA + + sym_values2 + base_data[24:]) + self.assertEqual(expected, data) def testSymbols(self): """Test binman can assign symbols embedded in U-Boot""" @@ -4181,7 +4232,8 @@ class TestFunctional(unittest.TestCase): data = self._DoReadFile('172_scp.dts') self.assertEqual(SCP_DATA, data[:len(SCP_DATA)]) - def CheckFitFdt(self, dts='170_fit_fdt.dts', use_fdt_list=True): + def CheckFitFdt(self, dts='170_fit_fdt.dts', use_fdt_list=True, + default_dt=None): """Check an image with an FIT with multiple FDT images""" def _CheckFdt(seq, expected_data): """Check the FDT nodes @@ -4225,6 +4277,8 @@ class TestFunctional(unittest.TestCase): } if use_fdt_list: entry_args['of-list'] = 'test-fdt1 test-fdt2' + if default_dt: + entry_args['default-dt'] = default_dt data = self._DoReadFileDtb( dts, entry_args=entry_args, @@ -7624,7 +7678,22 @@ fdt fdtmap Extract the devicetree blob from the fdtmap 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) + old_dir = os.getcwd() + try: + os.chdir(self._indir) + self.CheckFitFdt('333_fit_fdt_dir.dts', False) + finally: + os.chdir(old_dir) + + def testFitFdtListDirDefault(self): + """Test an FIT fit,fdt-list-dir where the default DT in is a subdir""" + old_dir = os.getcwd() + try: + os.chdir(self._indir) + self.CheckFitFdt('333_fit_fdt_dir.dts', False, + default_dt='rockchip/test-fdt2') + finally: + os.chdir(old_dir) def testFitFdtCompat(self): """Test an image with an FIT with compatible in the config nodes""" @@ -7690,6 +7759,51 @@ fdt fdtmap Extract the devicetree blob from the fdtmap # Make sure the other node is gone self.assertIsNone(dtb.GetNode('/node/other-node')) + def testMkeficapsuleMissing(self): + """Test that binman complains if mkeficapsule is missing""" + with self.assertRaises(ValueError) as e: + self._DoTestFile('311_capsule.dts', + force_missing_bintools='mkeficapsule') + self.assertIn("Node '/binman/efi-capsule': Missing tool: 'mkeficapsule'", + str(e.exception)) + + def testMkeficapsuleMissingOk(self): + """Test that binman deals with mkeficapsule being missing""" + with test_util.capture_sys_output() as (stdout, stderr): + ret = self._DoTestFile('311_capsule.dts', + force_missing_bintools='mkeficapsule', + allow_missing=True) + self.assertEqual(103, ret) + err = stderr.getvalue() + self.assertRegex(err, "Image 'image'.*missing bintools.*: mkeficapsule") + + def testSymbolsBase(self): + """Test handling of symbols-base""" + self.checkSymbols('336_symbols_base.dts', U_BOOT_SPL_DATA, 0x1c, + symbols_base=0) + + def testSymbolsBaseExpanded(self): + """Test handling of symbols-base with expanded entries""" + entry_args = { + 'spl-dtb': '1', + } + self.checkSymbols('337_symbols_base_expand.dts', U_BOOT_SPL_NODTB_DATA + + U_BOOT_SPL_DTB_DATA, 0x38, + entry_args=entry_args, use_expanded=True, + symbols_base=0) + + def testSymbolsCompressed(self): + """Test binman complains about symbols from a compressed section""" + with test_util.capture_sys_output() as (stdout, stderr): + self.checkSymbols('338_symbols_comp.dts', U_BOOT_SPL_DATA, None) + out = stdout.getvalue() + self.assertIn('Symbol-writing: no value for /binman/section/u-boot', + out) + + def testNxpImx8Image(self): + """Test that binman can produce an iMX8 image""" + self._DoTestFile('339_nxp_imx8.dts') + if __name__ == "__main__": unittest.main() diff --git a/tools/binman/image.py b/tools/binman/image.py index 702c9055585..24ce0af7c72 100644 --- a/tools/binman/image.py +++ b/tools/binman/image.py @@ -381,11 +381,10 @@ class Image(section.Entry_section): selected_entries.append(entry) return selected_entries, lines, widths - def LookupImageSymbol(self, sym_name, optional, msg, base_addr): - """Look up a symbol in an ELF file + def GetImageSymbolValue(self, sym_name, optional, msg, base_addr): + """Get the value of a Binman symbol - Looks up a symbol in an ELF file. Only entry types which come from an - ELF image can be used by this function. + Look up a Binman symbol and obtain its value. This searches through this image including all of its subsections. @@ -405,12 +404,10 @@ class Image(section.Entry_section): optional: True if the symbol is optional. If False this function will raise if the symbol is not found msg: Message to display if an error occurs - base_addr: Base address of image. This is added to the returned - image_pos in most cases so that the returned position indicates - where the targeted entry/binary has actually been loaded. But - if end-at-4gb is used, this is not done, since the binary is - already assumed to be linked to the ROM position and using - execute-in-place (XIP). + base_addr (int): Base address of image. This is added to the + returned value of image-pos so that the returned position + indicates where the targeted entry/binary has actually been + loaded Returns: Value that should be assigned to that symbol, or None if it was @@ -423,8 +420,8 @@ class Image(section.Entry_section): entries = OrderedDict() entries_by_name = {} self._CollectEntries(entries, entries_by_name, self) - return self.LookupSymbol(sym_name, optional, msg, base_addr, - entries_by_name) + return self.GetSymbolValue(sym_name, optional, msg, base_addr, + entries_by_name) def CollectBintools(self): """Collect all the bintools used by this image diff --git a/tools/binman/image_test.py b/tools/binman/image_test.py index bd51c1e55d1..7d65e2d589a 100644 --- a/tools/binman/image_test.py +++ b/tools/binman/image_test.py @@ -13,7 +13,7 @@ class TestImage(unittest.TestCase): def testInvalidFormat(self): image = Image('name', 'node', test=True) with self.assertRaises(ValueError) as e: - image.LookupSymbol('_binman_something_prop_', False, 'msg', 0) + image.GetSymbolValue('_binman_something_prop_', False, 'msg', 0) self.assertIn( "msg: Symbol '_binman_something_prop_' has invalid format", str(e.exception)) @@ -22,7 +22,7 @@ class TestImage(unittest.TestCase): image = Image('name', 'node', test=True) image._entries = {} with self.assertRaises(ValueError) as e: - image.LookupSymbol('_binman_type_prop_pname', False, 'msg', 0) + image.GetSymbolValue('_binman_type_prop_pname', False, 'msg', 0) self.assertIn("msg: Entry 'type' not found in list ()", str(e.exception)) @@ -30,7 +30,7 @@ class TestImage(unittest.TestCase): image = Image('name', 'node', test=True) image._entries = {} with capture_sys_output() as (stdout, stderr): - val = image.LookupSymbol('_binman_type_prop_pname', True, 'msg', 0) + val = image.GetSymbolValue('_binman_type_prop_pname', True, 'msg', 0) self.assertEqual(val, None) self.assertEqual("Warning: msg: Entry 'type' not found in list ()\n", stderr.getvalue()) @@ -40,5 +40,5 @@ class TestImage(unittest.TestCase): image = Image('name', 'node', test=True) image._entries = {'u-boot': 1} with self.assertRaises(ValueError) as e: - image.LookupSymbol('_binman_u_boot_prop_bad', False, 'msg', 0) + image.GetSymbolValue('_binman_u_boot_prop_bad', False, 'msg', 0) self.assertIn("msg: No such property 'bad", str(e.exception)) diff --git a/tools/binman/test/336_symbols_base.dts b/tools/binman/test/336_symbols_base.dts new file mode 100644 index 00000000000..e4dccd38c22 --- /dev/null +++ b/tools/binman/test/336_symbols_base.dts @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + pad-byte = <0xff>; + u-boot-spl { + symbols-base = <0>; + }; + + u-boot { + offset = <0x1c>; + }; + + u-boot-spl2 { + type = "u-boot-spl"; + }; + }; +}; diff --git a/tools/binman/test/337_symbols_base_expand.dts b/tools/binman/test/337_symbols_base_expand.dts new file mode 100644 index 00000000000..5a777ae63b8 --- /dev/null +++ b/tools/binman/test/337_symbols_base_expand.dts @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + pad-byte = <0xff>; + u-boot-spl { + symbols-base = <0>; + }; + + u-boot { + offset = <0x38>; + no-expanded; + }; + + u-boot-spl2 { + type = "u-boot-spl"; + }; + }; +}; diff --git a/tools/binman/test/338_symbols_comp.dts b/tools/binman/test/338_symbols_comp.dts new file mode 100644 index 00000000000..15008507cfd --- /dev/null +++ b/tools/binman/test/338_symbols_comp.dts @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + pad-byte = <0xff>; + u-boot-spl { + }; + + section { + offset = <0x1c>; + compress = "lz4"; + + u-boot { + }; + }; + + u-boot-spl2 { + type = "u-boot-spl"; + }; + }; +}; diff --git a/tools/binman/test/339_nxp_imx8.dts b/tools/binman/test/339_nxp_imx8.dts new file mode 100644 index 00000000000..cb512ae9aa2 --- /dev/null +++ b/tools/binman/test/339_nxp_imx8.dts @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/dts-v1/; + +/ { + #address-cells = <1>; + #size-cells = <1>; + + binman { + nxp-imx8mimage { + args; /* TODO: Needed by mkimage etype superclass */ + nxp,boot-from = "sd"; + nxp,rom-version = <1>; + nxp,loader-address = <0x10>; + }; + }; +}; diff --git a/tools/buildman/bsettings.py b/tools/buildman/bsettings.py index aea724fc559..a7358cfc08a 100644 --- a/tools/buildman/bsettings.py +++ b/tools/buildman/bsettings.py @@ -31,6 +31,9 @@ def setup(fname=''): def add_file(data): settings.read_file(io.StringIO(data)) +def add_section(name): + settings.add_section(name) + def get_items(section): """Get the items from a section of the config. diff --git a/tools/buildman/builder.py b/tools/buildman/builder.py index c4384f53e8d..4090d328b30 100644 --- a/tools/buildman/builder.py +++ b/tools/buildman/builder.py @@ -22,6 +22,7 @@ from buildman import toolchain from patman import gitutil from u_boot_pylib import command from u_boot_pylib import terminal +from u_boot_pylib import tools from u_boot_pylib.terminal import tprint # This indicates an new int or hex Kconfig property with no default @@ -263,7 +264,8 @@ class Builder: adjust_cfg=None, allow_missing=False, no_lto=False, reproducible_builds=False, force_build=False, force_build_failures=False, force_reconfig=False, - in_tree=False, force_config_on_failure=False, make_func=None): + in_tree=False, force_config_on_failure=False, make_func=None, + dtc_skip=False): """Create a new Builder object Args: @@ -312,6 +314,7 @@ class Builder: force_config_on_failure (bool): Reconfigure the build before retrying a failed build make_func (function): Function to call to run 'make' + dtc_skip (bool): True to skip building dtc and use the system one """ self.toolchains = toolchains self.base_dir = base_dir @@ -354,6 +357,12 @@ class Builder: self.in_tree = in_tree self.force_config_on_failure = force_config_on_failure self.fallback_mrproper = fallback_mrproper + if dtc_skip: + self.dtc = shutil.which('dtc') + if not self.dtc: + raise ValueError('Cannot find dtc') + else: + self.dtc = None if not self.squash_config_y: self.config_filenames += EXTRA_CONFIG_FILENAMES @@ -407,6 +416,22 @@ class Builder: def signal_handler(self, signal, frame): sys.exit(1) + def make_environment(self, toolchain): + """Create the environment to use for building + + Args: + toolchain (Toolchain): Toolchain to use for building + + Returns: + dict: + key (str): Variable name + value (str): Variable value + """ + env = toolchain.MakeEnvironment(self.full_path) + if self.dtc: + env[b'DTC'] = tools.to_bytes(self.dtc) + return env + def set_display_options(self, show_errors=False, show_sizes=False, show_detail=False, show_bloat=False, list_error_boards=False, show_config=False, diff --git a/tools/buildman/builderthread.py b/tools/buildman/builderthread.py index bbe2f6f0d24..b5afee61aff 100644 --- a/tools/buildman/builderthread.py +++ b/tools/buildman/builderthread.py @@ -406,7 +406,7 @@ class BuilderThread(threading.Thread): the next incremental build """ # Set up the environment and command line - env = self.toolchain.MakeEnvironment(self.builder.full_path) + env = self.builder.make_environment(self.toolchain) mkdir(out_dir) args, cwd, src_dir = self._build_args(brd, out_dir, out_rel_dir, @@ -574,7 +574,7 @@ class BuilderThread(threading.Thread): outf.write(f'{result.return_code}') # Write out the image and function size information and an objdump - env = result.toolchain.MakeEnvironment(self.builder.full_path) + env = self.builder.make_environment(self.toolchain) with open(os.path.join(build_dir, 'out-env'), 'wb') as outf: for var in sorted(env.keys()): outf.write(b'%s="%s"' % (var, env[var])) @@ -755,6 +755,14 @@ class BuilderThread(threading.Thread): self.mrproper, self.builder.config_only, True, self.builder.force_build_failures, job.work_in_output, job.adjust_cfg) + failed = result.return_code or result.stderr + if failed and not self.mrproper: + result, request_config = self.run_commit(None, brd, work_dir, + True, self.builder.fallback_mrproper, + self.builder.config_only, True, + self.builder.force_build_failures, + job.work_in_output, job.adjust_cfg) + result.commit_upto = 0 self._write_result(result, job.keep_outputs, job.work_in_output) self._send_result(result) diff --git a/tools/buildman/buildman.rst b/tools/buildman/buildman.rst index b8ff3bf1ab2..e873611e596 100644 --- a/tools/buildman/buildman.rst +++ b/tools/buildman/buildman.rst @@ -1030,6 +1030,9 @@ of the source tree, thus allowing rapid tested evolution of the code:: ./tools/buildman/buildman -Pr tegra +Note also the `--dtc-skip` option which uses the system device-tree compiler to +avoid needing to build it for each board. This can save 10-20% of build time. +An alternative is to set DTC=/path/to/dtc when running buildman. Checking configuration ---------------------- diff --git a/tools/buildman/cmdline.py b/tools/buildman/cmdline.py index 544a391a464..7573e5bdfe8 100644 --- a/tools/buildman/cmdline.py +++ b/tools/buildman/cmdline.py @@ -46,6 +46,8 @@ def add_upto_m(parser): help='Show detailed size delta for each board in the -S summary') parser.add_argument('-D', '--debug', action='store_true', help='Enabling debugging (provides a full traceback on error)') + parser.add_argument('--dtc-skip', action='store_true', default=False, + help='Skip building of dtc and use the system version') parser.add_argument('-e', '--show_errors', action='store_true', default=False, help='Show errors and warnings') parser.add_argument('-E', '--warnings-as-errors', action='store_true', diff --git a/tools/buildman/control.py b/tools/buildman/control.py index d3d027f02ab..55d4d770c5c 100644 --- a/tools/buildman/control.py +++ b/tools/buildman/control.py @@ -809,7 +809,8 @@ def do_buildman(args, toolchains=None, make_func=None, brds=None, force_build = args.force_build, force_build_failures = args.force_build_failures, force_reconfig = args.force_reconfig, in_tree = args.in_tree, - force_config_on_failure=not args.quick, make_func=make_func) + force_config_on_failure=not args.quick, make_func=make_func, + dtc_skip=args.dtc_skip) TEST_BUILDER = builder diff --git a/tools/buildman/kconfiglib.py b/tools/buildman/kconfiglib.py index b9f37567559..27abbf9a7a1 100644 --- a/tools/buildman/kconfiglib.py +++ b/tools/buildman/kconfiglib.py @@ -6,7 +6,7 @@ Overview ======== Kconfiglib is a Python 2/3 library for scripting and extracting information -from Kconfig (https://www.kernel.org/doc/Documentation/kbuild/kconfig-language.txt) +from Kconfig (https://www.kernel.org/doc/Documentation/kbuild/kconfig-language.rst) configuration systems. See the homepage at https://github.com/ulfalizer/Kconfiglib for a longer @@ -709,7 +709,7 @@ class Kconfig(object): mainmenu_text: The prompt (title) of the top menu (top_node). Defaults to "Main menu". - Can be changed with the 'mainmenu' statement (see kconfig-language.txt). + Can be changed with the 'mainmenu' statement (see kconfig-language.rst). variables: A dictionary with all preprocessor variables, indexed by name. See the @@ -3562,7 +3562,7 @@ class Kconfig(object): # # - Propagates dependencies from parent to child nodes # - # - Creates implicit menus (see kconfig-language.txt) + # - Creates implicit menus (see kconfig-language.rst) # # - Removes 'if' nodes # @@ -5030,7 +5030,7 @@ class Choice(object): 0 (n) - The choice is disabled and no symbols can be selected. For visible choices, this mode is only possible for choices with - the 'optional' flag set (see kconfig-language.txt). + the 'optional' flag set (see kconfig-language.rst). 1 (m) - Any number of choice symbols can be set to m, the rest will be n. @@ -5498,7 +5498,7 @@ class MenuNode(object): Choices and menus naturally have children, but Symbols can also have children because of menus created automatically from dependencies (see - kconfig-language.txt). + kconfig-language.rst). parent: The parent menu node. None if there is no parent. diff --git a/tools/buildman/test.py b/tools/buildman/test.py index bfad3093030..15801f6097f 100644 --- a/tools/buildman/test.py +++ b/tools/buildman/test.py @@ -36,6 +36,16 @@ main: /usr/sbin x86: i386 x86_64 ''' +settings_data_wrapper = ''' +# Buildman settings file + +[toolchain] +main: /usr/sbin + +[toolchain-wrapper] +wrapper = ccache +''' + migration = '''===================== WARNING ====================== This board does not use CONFIG_DM. CONFIG_DM will be compulsory starting with the v2020.01 release. @@ -148,6 +158,7 @@ class TestBuild(unittest.TestCase): self.toolchains.Add('arm-linux-gcc', test=False) self.toolchains.Add('sparc-linux-gcc', test=False) self.toolchains.Add('powerpc-linux-gcc', test=False) + self.toolchains.Add('/path/to/aarch64-linux-gcc', test=False) self.toolchains.Add('gcc', test=False) # Avoid sending any output @@ -605,6 +616,9 @@ class TestBuild(unittest.TestCase): tc.GetEnvArgs(toolchain.VAR_ARCH)) self.assertEqual('', tc.GetEnvArgs(toolchain.VAR_MAKE_ARGS)) + tc = self.toolchains.Select('sandbox') + self.assertEqual('', tc.GetEnvArgs(toolchain.VAR_CROSS_COMPILE)) + self.toolchains.Add('/path/to/x86_64-linux-gcc', test=False) tc = self.toolchains.Select('x86') self.assertEqual('/path/to', @@ -613,6 +627,39 @@ class TestBuild(unittest.TestCase): self.assertEqual('HOSTCC=clang CC=clang', tc.GetEnvArgs(toolchain.VAR_MAKE_ARGS)) + # Test config with ccache wrapper + bsettings.setup(None) + bsettings.add_file(settings_data_wrapper) + + tc = self.toolchains.Select('arm') + self.assertEqual('ccache arm-linux-', + tc.GetEnvArgs(toolchain.VAR_CROSS_COMPILE)) + + tc = self.toolchains.Select('sandbox') + self.assertEqual('', tc.GetEnvArgs(toolchain.VAR_CROSS_COMPILE)) + + def testMakeEnvironment(self): + """Test the MakeEnvironment function""" + tc = self.toolchains.Select('arm') + env = tc.MakeEnvironment(False) + self.assertEqual(env[b'CROSS_COMPILE'], b'arm-linux-') + + tc = self.toolchains.Select('sandbox') + env = tc.MakeEnvironment(False) + self.assertTrue(b'CROSS_COMPILE' not in env) + + # Test config with ccache wrapper + bsettings.setup(None) + bsettings.add_file(settings_data_wrapper) + + tc = self.toolchains.Select('arm') + env = tc.MakeEnvironment(False) + self.assertEqual(env[b'CROSS_COMPILE'], b'ccache arm-linux-') + + tc = self.toolchains.Select('sandbox') + env = tc.MakeEnvironment(False) + self.assertTrue(b'CROSS_COMPILE' not in env) + def testPrepareOutputSpace(self): def _Touch(fname): tools.write_file(os.path.join(base_dir, fname), b'') @@ -779,6 +826,7 @@ class TestBuild(unittest.TestCase): tmpdir = self.base_dir with (patch('time.time', side_effect=self.get_time), + patch('time.monotonic', side_effect=self.get_time), patch('time.sleep', side_effect=self.inc_time), patch('os.kill', side_effect=self.kill)): # Grab the process. Since there is no other profcess, this should @@ -868,6 +916,120 @@ class TestBuild(unittest.TestCase): self.assertEqual([4, 5], control.read_procs(tmpdir)) self.assertEqual(self.finish_time, self.cur_time) + def call_make_environment(self, tchn, full_path, in_env=None): + """Call Toolchain.MakeEnvironment() and process the result + + Args: + tchn (Toolchain): Toolchain to use + full_path (bool): True to return the full path in CROSS_COMPILE + rather than adding it to the PATH variable + in_env (dict): Input environment to use, None to use current env + + Returns: + tuple: + dict: Changes that MakeEnvironment has made to the environment + key: Environment variable that was changed + value: New value (for PATH this only includes components + which were added) + str: Full value of the new PATH variable + """ + env = tchn.MakeEnvironment(full_path, env=in_env) + + # Get the original environment + orig_env = dict(os.environb if in_env is None else in_env) + orig_path = orig_env[b'PATH'].split(b':') + + # Find new variables + diff = dict((k, env[k]) for k in env if orig_env.get(k) != env[k]) + + # Find new / different path components + diff_path = None + new_path = None + if b'PATH' in diff: + new_path = diff[b'PATH'].split(b':') + diff_paths = [p for p in new_path if p not in orig_path] + diff_path = b':'.join(p for p in new_path if p not in orig_path) + if diff_path: + diff[b'PATH'] = diff_path + else: + del diff[b'PATH'] + return diff, new_path + + def test_toolchain_env(self): + """Test PATH and other environment settings for toolchains""" + # Use a toolchain which has a path, so that full_path makes a difference + tchn = self.toolchains.Select('aarch64') + + # Normal cases + diff = self.call_make_environment(tchn, full_path=False)[0] + self.assertEqual( + {b'CROSS_COMPILE': b'aarch64-linux-', b'LC_ALL': b'C', + b'PATH': b'/path/to'}, diff) + + diff = self.call_make_environment(tchn, full_path=True)[0] + self.assertEqual( + {b'CROSS_COMPILE': b'/path/to/aarch64-linux-', b'LC_ALL': b'C'}, + diff) + + # When overriding the toolchain, only LC_ALL should be set + tchn.override_toolchain = True + diff = self.call_make_environment(tchn, full_path=True)[0] + self.assertEqual({b'LC_ALL': b'C'}, diff) + + # Test that virtualenv is handled correctly + tchn.override_toolchain = False + sys.prefix = '/some/venv' + env = dict(os.environb) + env[b'PATH'] = b'/some/venv/bin:other/things' + tchn.path = '/my/path' + diff, diff_path = self.call_make_environment(tchn, False, env) + + self.assertIn(b'PATH', diff) + self.assertEqual([b'/some/venv/bin', b'/my/path', b'other/things'], + diff_path) + self.assertEqual( + {b'CROSS_COMPILE': b'aarch64-linux-', b'LC_ALL': b'C', + b'PATH': b'/my/path'}, diff) + + # Handle a toolchain wrapper + tchn.path = '' + bsettings.add_section('toolchain-wrapper') + bsettings.set_item('toolchain-wrapper', 'my-wrapper', 'fred') + diff = self.call_make_environment(tchn, full_path=True)[0] + self.assertEqual( + {b'CROSS_COMPILE': b'fred aarch64-linux-', b'LC_ALL': b'C'}, diff) + + def test_skip_dtc(self): + """Test skipping building the dtc tool""" + old_path = os.getenv('PATH') + try: + os.environ['PATH'] = self.base_dir + + # Check a missing tool + with self.assertRaises(ValueError) as exc: + builder.Builder(self.toolchains, self.base_dir, None, 0, 2, + dtc_skip=True) + self.assertIn('Cannot find dtc', str(exc.exception)) + + # Create a fake tool to use + dtc = os.path.join(self.base_dir, 'dtc') + tools.write_file(dtc, b'xx') + os.chmod(dtc, 0o777) + + build = builder.Builder(self.toolchains, self.base_dir, None, 0, 2, + dtc_skip=True) + toolchain = self.toolchains.Select('arm') + env = build.make_environment(toolchain) + self.assertIn(b'DTC', env) + + # Try the normal case, i.e. not skipping the dtc build + build = builder.Builder(self.toolchains, self.base_dir, None, 0, 2) + toolchain = self.toolchains.Select('arm') + env = build.make_environment(toolchain) + self.assertNotIn(b'DTC', env) + finally: + os.environ['PATH'] = old_path + if __name__ == "__main__": unittest.main() diff --git a/tools/buildman/toolchain.py b/tools/buildman/toolchain.py index 324ad0e0821..a7d7883b851 100644 --- a/tools/buildman/toolchain.py +++ b/tools/buildman/toolchain.py @@ -159,6 +159,8 @@ class Toolchain: if which == VAR_CROSS_COMPILE: wrapper = self.GetWrapper() base = '' if self.arch == 'sandbox' else self.path + if (base == '' and self.cross == ''): + return '' return wrapper + os.path.join(base, self.cross) elif which == VAR_PATH: return self.path @@ -172,13 +174,14 @@ class Toolchain: else: raise ValueError('Unknown arg to GetEnvArgs (%d)' % which) - def MakeEnvironment(self, full_path): + def MakeEnvironment(self, full_path, env=None): """Returns an environment for using the toolchain. This takes the current environment and adds CROSS_COMPILE so that the tool chain will operate correctly. This also disables localized output and possibly Unicode encoded output of all build tools by - adding LC_ALL=C. + adding LC_ALL=C. For the case where full_path is False, it prepends + the toolchain to PATH Note that os.environb is used to obtain the environment, since in some cases the environment many contain non-ASCII characters and we see @@ -187,26 +190,48 @@ class Toolchain: UnicodeEncodeError: 'utf-8' codec can't encode characters in position 569-570: surrogates not allowed + When running inside a Python venv, care is taken not to put the + toolchain path before the venv path, so that builds initiated by + buildman will still respect the venv. + Args: full_path: Return the full path in CROSS_COMPILE and don't set PATH + env (dict of bytes): Original environment, used for testing Returns: Dict containing the (bytes) environment to use. This is based on the current environment, with changes as needed to CROSS_COMPILE, PATH and LC_ALL. """ - env = dict(os.environb) + env = dict(env or os.environb) + wrapper = self.GetWrapper() if self.override_toolchain: # We'll use MakeArgs() to provide this pass - elif full_path: + elif full_path and self.cross: env[b'CROSS_COMPILE'] = tools.to_bytes( wrapper + os.path.join(self.path, self.cross)) - else: + elif self.cross: env[b'CROSS_COMPILE'] = tools.to_bytes(wrapper + self.cross) - env[b'PATH'] = tools.to_bytes(self.path) + b':' + env[b'PATH'] + + # Detect a Python virtualenv and avoid defeating it + if sys.prefix != sys.base_prefix: + paths = env[b'PATH'].split(b':') + new_paths = [] + to_insert = tools.to_bytes(self.path) + insert_after = tools.to_bytes(sys.prefix) + for path in paths: + new_paths.append(path) + if to_insert and path.startswith(insert_after): + new_paths.append(to_insert) + to_insert = None + if to_insert: + new_paths.append(to_insert) + env[b'PATH'] = b':'.join(new_paths) + else: + env[b'PATH'] = tools.to_bytes(self.path) + b':' + env[b'PATH'] env[b'LC_ALL'] = b'C' diff --git a/tools/eficapsule.h b/tools/eficapsule.h index 6efd07d2eb6..97d077536d5 100644 --- a/tools/eficapsule.h +++ b/tools/eficapsule.h @@ -24,7 +24,7 @@ #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) -typedef struct { +typedef struct efi_guid { uint8_t b[16]; } efi_guid_t __aligned(8); diff --git a/tools/image-sig-host.c b/tools/image-sig-host.c index d0133aec4c8..21b4fa5d39d 100644 --- a/tools/image-sig-host.c +++ b/tools/image-sig-host.c @@ -76,6 +76,13 @@ struct crypto_algo crypto_algos[] = { .add_verify_data = ecdsa_add_verify_data, .verify = ecdsa_verify, }, + { + .name = "secp521r1", + .key_len = ECDSA521_BYTES, + .sign = ecdsa_sign, + .add_verify_data = ecdsa_add_verify_data, + .verify = ecdsa_verify, + }, }; struct padding_algo padding_algos[] = { diff --git a/tools/mkeficapsule.c b/tools/mkeficapsule.c index 1b53151d41a..49f5b7849e4 100644 --- a/tools/mkeficapsule.c +++ b/tools/mkeficapsule.c @@ -16,16 +16,20 @@ #include <sys/stat.h> #include <sys/types.h> -#include <uuid/uuid.h> #include <gnutls/gnutls.h> #include <gnutls/pkcs7.h> #include <gnutls/abstract.h> #include <version.h> +#include <libfdt.h> +#include <u-boot/uuid.h> #include "eficapsule.h" +// Matches CONFIG_EFI_CAPSULE_NAMESPACE_GUID +#define DEFAULT_NAMESPACE_GUID "8c9f137e-91dc-427b-b2d6-b420faebaf2a" + static const char *tool_name = "mkeficapsule"; efi_guid_t efi_guid_fm_capsule = EFI_FIRMWARE_MANAGEMENT_CAPSULE_ID_GUID; @@ -56,9 +60,20 @@ static struct option options[] = { {NULL, 0, NULL, 0}, }; -static void print_usage(void) +static void print_usage_guidgen(void) { - fprintf(stderr, "Usage: %s [options] <image blob> <output file>\n" + fprintf(stderr, "%s guidgen [GUID] DTB IMAGE_NAME...\n" + "Options:\n" + + "\tGUID Namespace GUID (default: %s)\n" + "\tDTB Device Tree Blob\n" + "\tIMAGE_NAME... One or more names of fw_images to generate GUIDs for\n", + tool_name, DEFAULT_NAMESPACE_GUID); +} + +static void print_usage_mkeficapsule(void) +{ + fprintf(stderr, "Usage:\n\n%s [options] <image blob> <output file>\n" "Options:\n" "\t-g, --guid <guid string> guid for image blob type\n" @@ -74,8 +89,9 @@ static void print_usage(void) "\t-o, --capoemflag Capsule OEM Flag, an integer between 0x0000 and 0xffff\n" "\t-D, --dump-capsule dump the contents of the capsule headers\n" "\t-V, --version show version number\n" - "\t-h, --help print a help message\n", + "\t-h, --help print a help message\n\n", tool_name); + print_usage_guidgen(); } /** @@ -578,37 +594,6 @@ err: return ret; } -/** - * convert_uuid_to_guid() - convert UUID to GUID - * @buf: UUID binary - * - * UUID and GUID have the same data structure, but their binary - * formats are different due to the endianness. See lib/uuid.c. - * Since uuid_parse() can handle only UUID, this function must - * be called to get correct data for GUID when parsing a string. - * - * The correct data will be returned in @buf. - */ -void convert_uuid_to_guid(unsigned char *buf) -{ - unsigned char c; - - c = buf[0]; - buf[0] = buf[3]; - buf[3] = c; - c = buf[1]; - buf[1] = buf[2]; - buf[2] = c; - - c = buf[4]; - buf[4] = buf[5]; - buf[5] = c; - - c = buf[6]; - buf[6] = buf[7]; - buf[7] = c; -} - static int create_empty_capsule(char *path, efi_guid_t *guid, bool fw_accept) { struct efi_capsule_header header = { 0 }; @@ -654,20 +639,10 @@ err: static void print_guid(void *ptr) { - int i; - efi_guid_t *guid = ptr; - const uint8_t seq[] = { - 3, 2, 1, 0, '-', 5, 4, '-', 7, 6, - '-', 8, 9, '-', 10, 11, 12, 13, 14, 15 }; - - for (i = 0; i < ARRAY_SIZE(seq); i++) { - if (seq[i] == '-') - putchar(seq[i]); - else - printf("%02X", guid->b[seq[i]]); - } + static char buf[37] = { 0 }; - printf("\n"); + uuid_bin_to_str(ptr, buf, UUID_STR_FORMAT_GUID | UUID_STR_UPPER_CASE); + printf("%s\n", buf); } static uint32_t dump_fmp_payload_header( @@ -861,6 +836,129 @@ static void dump_capsule_contents(char *capsule_file) } } +static struct fdt_header *load_dtb(const char *path) +{ + struct fdt_header *dtb; + ssize_t dtb_size; + FILE *f; + + /* Open and parse DTB */ + f = fopen(path, "r"); + if (!f) { + fprintf(stderr, "Cannot open %s\n", path); + return NULL; + } + + if (fseek(f, 0, SEEK_END)) { + fprintf(stderr, "Cannot seek to the end of %s: %s\n", + path, strerror(errno)); + return NULL; + } + + dtb_size = ftell(f); + if (dtb_size < 0) { + fprintf(stderr, "Cannot ftell %s: %s\n", + path, strerror(errno)); + return NULL; + } + + fseek(f, 0, SEEK_SET); + + dtb = malloc(dtb_size); + if (!dtb) { + fprintf(stderr, "Can't allocated %ld\n", dtb_size); + return NULL; + } + + if (fread(dtb, dtb_size, 1, f) != 1) { + fprintf(stderr, "Can't read %ld bytes from %s\n", + dtb_size, path); + free(dtb); + return NULL; + } + + fclose(f); + + return dtb; +} + +#define MAX_IMAGE_NAME_LEN 128 +static int genguid(int argc, char **argv) +{ + int idx = 2, ret; + unsigned char namespace[16]; + struct efi_guid image_type_id; + const char *dtb_path; + struct fdt_header *dtb; + const char *compatible; + int compatlen, namelen; + uint16_t fw_image[MAX_IMAGE_NAME_LEN]; + + if (argc < 2) { + fprintf(stderr, "Usage: "); + print_usage_guidgen(); + return -1; + } + + if (uuid_str_to_bin(argv[1], namespace, UUID_STR_FORMAT_GUID)) { + uuid_str_to_bin(DEFAULT_NAMESPACE_GUID, namespace, UUID_STR_FORMAT_GUID); + dtb_path = argv[1]; + } else { + dtb_path = argv[2]; + idx = 3; + } + + if (idx == argc) { + fprintf(stderr, "Usage: "); + print_usage_guidgen(); + return -1; + } + + dtb = load_dtb(dtb_path); + if (!dtb) + return -1; + + ret = fdt_check_header(dtb); + if (ret) { + fprintf(stderr, "Invalid DTB header: %d\n", ret); + return -1; + } + + compatible = fdt_getprop(dtb, 0, "compatible", &compatlen); + if (!compatible) { + fprintf(stderr, "No compatible string found in DTB\n"); + return -1; + } + if (strnlen(compatible, compatlen) >= compatlen) { + fprintf(stderr, "Compatible string not null-terminated\n"); + return -1; + } + + printf("Generating GUIDs for %s with namespace %s:\n", + compatible, DEFAULT_NAMESPACE_GUID); + for (; idx < argc; idx++) { + memset(fw_image, 0, sizeof(fw_image)); + namelen = strlen(argv[idx]); + if (namelen > MAX_IMAGE_NAME_LEN) { + fprintf(stderr, "Image name too long: %s\n", argv[idx]); + return -1; + } + + for (int i = 0; i < namelen; i++) + fw_image[i] = (uint16_t)argv[idx][i]; + + gen_v5_guid((struct uuid *)&namespace, &image_type_id, + compatible, strlen(compatible), + fw_image, namelen * sizeof(uint16_t), + NULL); + + printf("%s: ", argv[idx]); + print_guid(&image_type_id); + } + + return 0; +} + /** * main - main entry function of mkeficapsule * @argc: Number of arguments @@ -885,6 +983,13 @@ int main(int argc, char **argv) int c, idx; struct fmp_payload_header_params fmp_ph_params = { 0 }; + /* Generate dynamic GUIDs */ + if (argc > 1 && !strcmp(argv[1], "guidgen")) { + if (genguid(argc - 1, argv + 1)) + exit(EXIT_FAILURE); + exit(EXIT_SUCCESS); + } + guid = NULL; index = 0; instance = 0; @@ -907,11 +1012,10 @@ int main(int argc, char **argv) "Image type already specified\n"); exit(EXIT_FAILURE); } - if (uuid_parse(optarg, uuid_buf)) { + if (uuid_str_to_bin(optarg, uuid_buf, UUID_STR_FORMAT_GUID)) { fprintf(stderr, "Wrong guid format\n"); exit(EXIT_FAILURE); } - convert_uuid_to_guid(uuid_buf); guid = (efi_guid_t *)uuid_buf; break; case 'i': @@ -977,7 +1081,7 @@ int main(int argc, char **argv) printf("mkeficapsule version %s\n", PLAIN_VERSION); exit(EXIT_SUCCESS); default: - print_usage(); + print_usage_mkeficapsule(); exit(EXIT_FAILURE); } } @@ -1000,7 +1104,7 @@ int main(int argc, char **argv) ((argc != optind + 1) || ((capsule_type == CAPSULE_ACCEPT) && !guid) || ((capsule_type == CAPSULE_REVERT) && guid)))) { - print_usage(); + print_usage_mkeficapsule(); exit(EXIT_FAILURE); } |