def _ReadSubnodes(self): def _AddNode(base_node, depth, node): """Add a node to the FIT Args: base_node: Base Node of the FIT (with 'description' property) depth: Current node depth (0 is the base node) node: Current node to process There are two cases to deal with: - hash and signature nodes which become part of the FIT - binman entries which are used to define the 'data' for each image """ for pname, prop in node.props.items(): if pname.startswith('fit,'): self._fit_props[pname] = prop else: fsw.property(pname, prop.bytes) rel_path = node.path[len(base_node.path):] has_images = depth == 2 and rel_path.startswith('/images/') for subnode in node.subnodes: if has_images and not (subnode.name.startswith('hash') or subnode.name.startswith('signature')): # This is a content node. We collect all of these together # and put them in the 'data' property. They do not appear # in the FIT. entry = Entry.Create(self.section, subnode) entry.ReadNode() self._fit_content[rel_path].append(entry) else: with fsw.add_node(subnode.name): _AddNode(base_node, depth + 1, subnode) # Build a new tree with all nodes and properties starting from the # entry node fsw = libfdt.FdtSw() fsw.finish_reservemap() with fsw.add_node(''): _AddNode(self._node, 0, self._node) fdt = fsw.as_fdt() # Pack this new FDT and scan it so we can add the data later fdt.pack() self._fdt = Fdt.FromData(fdt.as_bytearray()) self._fdt.Scan()
def _GetFdtmap(self): """Build an FDT map from the entries in the current image Returns: FDT map binary data """ def _AddNode(node): """Add a node to the FDT map""" for pname, prop in node.props.items(): fsw.property(pname, prop.bytes) for subnode in node.subnodes: with fsw.add_node(subnode.name): _AddNode(subnode) data = state.GetFdtContents('fdtmap')[1] # If we have an fdtmap it means that we are using this as the # fdtmap for this image. if data is None: # Get the FDT data into an Fdt object data = state.GetFdtContents()[1] infdt = Fdt.FromData(data) infdt.Scan() # Find the node for the image containing the Fdt-map entry path = self.section.GetPath() self.Detail("Fdtmap: Using section '%s' (path '%s')" % (self.section.name, path)) node = infdt.GetNode(path) if not node: self.Raise("Internal error: Cannot locate node for path '%s'" % path) # Build a new tree with all nodes and properties starting from that # node fsw = libfdt.FdtSw() fsw.finish_reservemap() with fsw.add_node(''): fsw.property_string('image-node', node.name) _AddNode(node) fdt = fsw.as_fdt() # Pack this new FDT and return its contents fdt.pack() outfdt = Fdt.FromData(fdt.as_bytearray()) data = outfdt.GetContents() data = FDTMAP_MAGIC + tools.GetBytes(0, 8) + data return data
def _ReadSubnodes(self): def _AddNode(base_node, depth, node): """Add a node to the FIT Args: base_node: Base Node of the FIT (with 'description' property) depth: Current node depth (0 is the base node) node: Current node to process There are two cases to deal with: - hash and signature nodes which become part of the FIT - binman entries which are used to define the 'data' for each image """ for pname, prop in node.props.items(): if not pname.startswith('fit,'): if pname == 'default': val = prop.value # Handle the 'default' property if val.startswith('@'): if not self._fdts: continue if not self._fit_default_dt: self.Raise( "Generated 'default' node requires default-dt entry argument" ) if self._fit_default_dt not in self._fdts: self.Raise( "default-dt entry argument '%s' not found in fdt list: %s" % (self._fit_default_dt, ', '.join( self._fdts))) seq = self._fdts.index(self._fit_default_dt) val = val[1:].replace('DEFAULT-SEQ', str(seq + 1)) fsw.property_string(pname, val) continue fsw.property(pname, prop.bytes) rel_path = node.path[len(base_node.path):] in_images = rel_path.startswith('/images') has_images = depth == 2 and in_images if has_images: # This node is a FIT subimage node (e.g. "/images/kernel") # containing content nodes. We collect the subimage nodes and # section entries for them here to merge the content subnodes # together and put the merged contents in the subimage node's # 'data' property later. entry = Entry.Create(self.section, node, etype='section') entry.ReadNode() self._fit_sections[rel_path] = entry for subnode in node.subnodes: if has_images and not (subnode.name.startswith('hash') or subnode.name.startswith('signature')): # This subnode is a content node not meant to appear in # the FIT (e.g. "/images/kernel/u-boot"), so don't call # fsw.add_node() or _AddNode() for it. pass elif subnode.name.startswith('@'): if self._fdts: # Generate notes for each FDT for seq, fdt_fname in enumerate(self._fdts): node_name = subnode.name[1:].replace( 'SEQ', str(seq + 1)) fname = tools.GetInputFilename(fdt_fname + '.dtb') with fsw.add_node(node_name): for pname, prop in subnode.props.items(): val = prop.bytes.replace( b'NAME', tools.ToBytes(fdt_fname)) val = val.replace( b'SEQ', tools.ToBytes(str(seq + 1))) fsw.property(pname, val) # Add data for 'fdt' nodes (but not 'config') if depth == 1 and in_images: fsw.property('data', tools.ReadFile(fname)) else: if self._fdts is None: if self._fit_list_prop: self.Raise( "Generator node requires '%s' entry argument" % self._fit_list_prop.value) else: self.Raise( "Generator node requires 'fit,fdt-list' property" ) else: with fsw.add_node(subnode.name): _AddNode(base_node, depth + 1, subnode) # Build a new tree with all nodes and properties starting from the # entry node fsw = libfdt.FdtSw() fsw.finish_reservemap() with fsw.add_node(''): _AddNode(self._node, 0, self._node) fdt = fsw.as_fdt() # Pack this new FDT and scan it so we can add the data later fdt.pack() self._fdt = Fdt.FromData(fdt.as_bytearray()) self._fdt.Scan()
def _build_input(self): """Finish the FIT by adding the 'data' properties to it Arguments: fdt: FIT to update Returns: bytes: New fdt contents """ def _process_prop(pname, prop): """Process special properties Handles properties with generated values. At present the only supported property is 'default', i.e. the default device tree in the configurations node. Args: pname (str): Name of property prop (Prop): Property to process """ if pname == 'default': val = prop.value # Handle the 'default' property if val.startswith('@'): if not self._fdts: return if not self._fit_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) val = val[1:].replace('DEFAULT-SEQ', str(seq + 1)) fsw.property_string(pname, val) return elif pname.startswith('fit,'): # Ignore these, which are commands for binman to process return elif pname in ['offset', 'size', 'image-pos']: # Don't add binman's calculated properties return fsw.property(pname, prop.bytes) def _gen_fdt_nodes(base_node, node, depth, in_images): """Generate FDT nodes This creates one node for each member of self._fdts using the provided template. If a property value contains 'NAME' it is replaced with the filename of the FDT. If a property value contains SEQ it is replaced with the node sequence number, where 1 is the first. Args: node (None): Generator node to process depth: Current node depth (0 is the base 'fit' node) in_images: True if this is inside the 'images' node, so that 'data' properties should be generated """ if self._fdts: # 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') with fsw.add_node(node_name): for pname, prop in node.props.items(): if pname == 'fit,loadables': val = '\0'.join(self._loadables) + '\0' fsw.property('loadables', val.encode('utf-8')) elif pname == 'fit,operation': pass elif pname.startswith('fit,'): self._raise_subnode( node, f"Unknown directive '{pname}'") else: val = prop.bytes.replace( b'NAME', tools.to_bytes(fdt_fname)) val = val.replace(b'SEQ', tools.to_bytes(str(seq + 1))) fsw.property(pname, val) # Add data for 'images' nodes (but not 'config') if depth == 1 and in_images: fsw.property('data', tools.read_file(fname)) for subnode in node.subnodes: with fsw.add_node(subnode.name): _add_node(node, depth + 1, subnode) else: if self._fdts is None: if self._fit_list_prop: self.Raise( 'Generator node requires ' f"'{self._fit_list_prop.value}' entry argument") else: self.Raise( "Generator node requires 'fit,fdt-list' property") def _gen_split_elf(base_node, node, elf_data, missing): """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) data (bytes): ELF-format data to process (may be empty) missing (bool): True if any of the data is missing """ # If any pieces are missing, skip this. The missing entries will # show an error if not missing: try: segments, entry = elf.read_loadable_segments(elf_data) except ValueError as exc: self._raise_subnode( node, f'Failed to read ELF file: {str(exc)}') for (seq, start, data) in segments: node_name = node.name[1:].replace('SEQ', str(seq + 1)) with fsw.add_node(node_name): loadables.append(node_name) for pname, prop in node.props.items(): if not pname.startswith('fit,'): fsw.property(pname, prop.bytes) elif pname == 'fit,load': fsw.property_u32('load', start) elif pname == 'fit,entry': if seq == 0: fsw.property_u32('entry', entry) elif pname == 'fit,data': fsw.property('data', bytes(data)) elif pname != 'fit,operation': self._raise_subnode( node, f"Unknown directive '{pname}'") def _gen_node(base_node, node, depth, in_images, entry): """Generate nodes from a template This creates one node for each member of self._fdts using the provided template. If a property value contains 'NAME' it is replaced with the filename of the FDT. If a property value contains SEQ it is replaced with the node sequence number, where 1 is the first. 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 that 'data' properties should be generated """ oper = self._get_operation(base_node, node) if oper == OP_GEN_FDT_NODES: _gen_fdt_nodes(base_node, node, depth, in_images) elif oper == OP_SPLIT_ELF: # Entry_section.ObtainContents() either returns True or # raises an exception. data = None missing_list = [] entry.ObtainContents() entry.Pack(0) data = entry.GetData() entry.CheckMissing(missing_list) _gen_split_elf(base_node, node, data, bool(missing_list)) def _add_node(base_node, depth, node): """Add nodes to the output FIT Args: base_node (Node): Base Node of the FIT (with 'description' property) depth (int): Current node depth (0 is the base 'fit' node) node (Node): Current node to process There are two cases to deal with: - hash and signature nodes which become part of the FIT - binman entries which are used to define the 'data' for each image, so don't appear in the FIT """ # Copy over all the relevant properties for pname, prop in node.props.items(): _process_prop(pname, prop) rel_path = node.path[len(base_node.path):] in_images = rel_path.startswith('/images') has_images = depth == 2 and in_images if has_images: entry = self._priv_entries[rel_path] data = entry.GetData() fsw.property('data', bytes(data)) for subnode in node.subnodes: subnode_path = f'{rel_path}/{subnode.name}' if has_images and not (subnode.name.startswith('hash') or subnode.name.startswith('signature')): # This subnode is a content node not meant to appear in # the FIT (e.g. "/images/kernel/u-boot"), so don't call # fsw.add_node() or _add_node() for it. pass elif self.GetImage().generate and subnode.name.startswith('@'): entry = self._priv_entries.get(subnode_path) _gen_node(base_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 to_remove.append(subnode_path) else: with fsw.add_node(subnode.name): _add_node(base_node, depth + 1, subnode) # Build a new tree with all nodes and properties starting from the # entry node fsw = libfdt.FdtSw() fsw.finish_reservemap() to_remove = [] loadables = [] with fsw.add_node(''): _add_node(self._node, 0, self._node) self._loadables = loadables fdt = fsw.as_fdt() # Remove generator entries from the main list for path in to_remove: if path in self._entries: del self._entries[path] # Pack this new FDT and scan it so we can add the data later fdt.pack() data = fdt.as_bytearray() return data
def ReadEntries(self): def _process_prop(pname, prop): """Process special properties Handles properties with generated values. At present the only supported property is 'default', i.e. the default device tree in the configurations node. Args: pname (str): Name of property prop (Prop): Property to process """ if pname == 'default': val = prop.value # Handle the 'default' property if val.startswith('@'): if not self._fdts: return if not self._fit_default_dt: self.Raise( "Generated 'default' node requires default-dt entry argument" ) if self._fit_default_dt not in self._fdts: self.Raise( "default-dt entry argument '%s' not found in fdt list: %s" % (self._fit_default_dt, ', '.join(self._fdts))) seq = self._fdts.index(self._fit_default_dt) val = val[1:].replace('DEFAULT-SEQ', str(seq + 1)) fsw.property_string(pname, val) return fsw.property(pname, prop.bytes) def _scan_gen_fdt_nodes(subnode, depth, in_images): """Generate FDT nodes This creates one node for each member of self._fdts using the provided template. If a property value contains 'NAME' it is replaced with the filename of the FDT. If a property value contains SEQ it is replaced with the node sequence number, where 1 is the first. Args: subnode (None): Generator node to process depth: Current node depth (0 is the base 'fit' node) in_images: True if this is inside the 'images' node, so that 'data' properties should be generated """ if self._fdts: # Generate nodes for each FDT for seq, fdt_fname in enumerate(self._fdts): node_name = subnode.name[1:].replace('SEQ', str(seq + 1)) fname = tools.get_input_filename(fdt_fname + '.dtb') with fsw.add_node(node_name): for pname, prop in subnode.props.items(): val = prop.bytes.replace(b'NAME', tools.to_bytes(fdt_fname)) val = val.replace(b'SEQ', tools.to_bytes(str(seq + 1))) fsw.property(pname, val) # Add data for 'images' nodes (but not 'config') if depth == 1 and in_images: fsw.property('data', tools.read_file(fname)) else: if self._fdts is None: if self._fit_list_prop: self.Raise( "Generator node requires '%s' entry argument" % self._fit_list_prop.value) else: self.Raise( "Generator node requires 'fit,fdt-list' property") def _scan_node(subnode, depth, in_images): """Generate nodes from a template This creates one node for each member of self._fdts using the provided template. If a property value contains 'NAME' it is replaced with the filename of the FDT. If a property value contains SEQ it is replaced with the node sequence number, where 1 is the first. Args: subnode (None): Generator node to process depth: Current node depth (0 is the base 'fit' node) in_images: True if this is inside the 'images' node, so that 'data' properties should be generated """ oper = self._get_operation(subnode) if oper == OP_GEN_FDT_NODES: _scan_gen_fdt_nodes(subnode, depth, in_images) def _AddNode(base_node, depth, node): """Add a node to the FIT Args: base_node: Base Node of the FIT (with 'description' property) depth: Current node depth (0 is the base 'fit' node) node: Current node to process There are two cases to deal with: - hash and signature nodes which become part of the FIT - binman entries which are used to define the 'data' for each image """ for pname, prop in node.props.items(): if not pname.startswith('fit,'): _process_prop(pname, prop) rel_path = node.path[len(base_node.path):] in_images = rel_path.startswith('/images') has_images = depth == 2 and in_images if has_images: # This node is a FIT subimage node (e.g. "/images/kernel") # containing content nodes. We collect the subimage nodes and # section entries for them here to merge the content subnodes # together and put the merged contents in the subimage node's # 'data' property later. entry = Entry.Create(self.section, node, etype='section') entry.ReadNode() # The hash subnodes here are for mkimage, not binman. entry.SetUpdateHash(False) self._entries[rel_path] = entry for subnode in node.subnodes: if has_images and not (subnode.name.startswith('hash') or subnode.name.startswith('signature')): # This subnode is a content node not meant to appear in # the FIT (e.g. "/images/kernel/u-boot"), so don't call # fsw.add_node() or _AddNode() for it. pass elif self.GetImage().generate and subnode.name.startswith('@'): _scan_node(subnode, depth, in_images) else: with fsw.add_node(subnode.name): _AddNode(base_node, depth + 1, subnode) # Build a new tree with all nodes and properties starting from the # entry node fsw = libfdt.FdtSw() fsw.finish_reservemap() with fsw.add_node(''): _AddNode(self._node, 0, self._node) fdt = fsw.as_fdt() # Pack this new FDT and scan it so we can add the data later fdt.pack() self._fdt = Fdt.FromData(fdt.as_bytearray()) self._fdt.Scan()