1# SPDX-License-Identifier: GPL-2.0+ 2# Copyright (c) 2016 Google, Inc 3# Written by Simon Glass <sjg@chromium.org> 4# 5# Entry-type module for producing a FIT 6# 7 8from collections import defaultdict, OrderedDict 9import libfdt 10 11from binman.entry import Entry, EntryArg 12from dtoc import fdt_util 13from dtoc.fdt import Fdt 14from patman import tools 15 16class Entry_fit(Entry): 17 """Flat Image Tree (FIT) 18 19 This calls mkimage to create a FIT (U-Boot Flat Image Tree) based on the 20 input provided. 21 22 Nodes for the FIT should be written out in the binman configuration just as 23 they would be in a file passed to mkimage. 24 25 For example, this creates an image containing a FIT with U-Boot SPL:: 26 27 binman { 28 fit { 29 description = "Test FIT"; 30 fit,fdt-list = "of-list"; 31 32 images { 33 kernel@1 { 34 description = "SPL"; 35 os = "u-boot"; 36 type = "rkspi"; 37 arch = "arm"; 38 compression = "none"; 39 load = <0>; 40 entry = <0>; 41 42 u-boot-spl { 43 }; 44 }; 45 }; 46 }; 47 }; 48 49 U-Boot supports creating fdt and config nodes automatically. To do this, 50 pass an of-list property (e.g. -a of-list=file1 file2). This tells binman 51 that you want to generates nodes for two files: file1.dtb and file2.dtb 52 The fit,fdt-list property (see above) indicates that of-list should be used. 53 If the property is missing you will get an error. 54 55 Then add a 'generator node', a node with a name starting with '@':: 56 57 images { 58 @fdt-SEQ { 59 description = "fdt-NAME"; 60 type = "flat_dt"; 61 compression = "none"; 62 }; 63 }; 64 65 This tells binman to create nodes fdt-1 and fdt-2 for each of your two 66 files. All the properties you specify will be included in the node. This 67 node acts like a template to generate the nodes. The generator node itself 68 does not appear in the output - it is replaced with what binman generates. 69 70 You can create config nodes in a similar way:: 71 72 configurations { 73 default = "@config-DEFAULT-SEQ"; 74 @config-SEQ { 75 description = "NAME"; 76 firmware = "atf"; 77 loadables = "uboot"; 78 fdt = "fdt-SEQ"; 79 }; 80 }; 81 82 This tells binman to create nodes config-1 and config-2, i.e. a config for 83 each of your two files. 84 85 Available substitutions for '@' nodes are: 86 87 SEQ: 88 Sequence number of the generated fdt (1, 2, ...) 89 NAME 90 Name of the dtb as provided (i.e. without adding '.dtb') 91 92 Note that if no devicetree files are provided (with '-a of-list' as above) 93 then no nodes will be generated. 94 95 The 'default' property, if present, will be automatically set to the name 96 if of configuration whose devicetree matches the 'default-dt' entry 97 argument, e.g. with '-a default-dt=sun50i-a64-pine64-lts'. 98 99 Available substitutions for '@' property values are 100 101 DEFAULT-SEQ: 102 Sequence number of the default fdt,as provided by the 'default-dt' entry 103 argument 104 105 Properties (in the 'fit' node itself): 106 fit,external-offset: Indicates that the contents of the FIT are external 107 and provides the external offset. This is passsed to mkimage via 108 the -E and -p flags. 109 110 """ 111 def __init__(self, section, etype, node): 112 """ 113 Members: 114 _fit: FIT file being built 115 _fit_sections: dict: 116 key: relative path to entry Node (from the base of the FIT) 117 value: Entry_section object comprising the contents of this 118 node 119 """ 120 super().__init__(section, etype, node) 121 self._fit = None 122 self._fit_sections = {} 123 self._fit_props = {} 124 for pname, prop in self._node.props.items(): 125 if pname.startswith('fit,'): 126 self._fit_props[pname] = prop 127 128 self._fdts = None 129 self._fit_list_prop = self._fit_props.get('fit,fdt-list') 130 if self._fit_list_prop: 131 fdts, = self.GetEntryArgsOrProps( 132 [EntryArg(self._fit_list_prop.value, str)]) 133 if fdts is not None: 134 self._fdts = fdts.split() 135 self._fit_default_dt = self.GetEntryArgsOrProps([EntryArg('default-dt', 136 str)])[0] 137 138 def ReadNode(self): 139 self._ReadSubnodes() 140 super().ReadNode() 141 142 def _ReadSubnodes(self): 143 def _AddNode(base_node, depth, node): 144 """Add a node to the FIT 145 146 Args: 147 base_node: Base Node of the FIT (with 'description' property) 148 depth: Current node depth (0 is the base node) 149 node: Current node to process 150 151 There are two cases to deal with: 152 - hash and signature nodes which become part of the FIT 153 - binman entries which are used to define the 'data' for each 154 image 155 """ 156 for pname, prop in node.props.items(): 157 if not pname.startswith('fit,'): 158 if pname == 'default': 159 val = prop.value 160 # Handle the 'default' property 161 if val.startswith('@'): 162 if not self._fdts: 163 continue 164 if not self._fit_default_dt: 165 self.Raise("Generated 'default' node requires default-dt entry argument") 166 if self._fit_default_dt not in self._fdts: 167 self.Raise("default-dt entry argument '%s' not found in fdt list: %s" % 168 (self._fit_default_dt, 169 ', '.join(self._fdts))) 170 seq = self._fdts.index(self._fit_default_dt) 171 val = val[1:].replace('DEFAULT-SEQ', str(seq + 1)) 172 fsw.property_string(pname, val) 173 continue 174 fsw.property(pname, prop.bytes) 175 176 rel_path = node.path[len(base_node.path):] 177 in_images = rel_path.startswith('/images') 178 has_images = depth == 2 and in_images 179 if has_images: 180 # This node is a FIT subimage node (e.g. "/images/kernel") 181 # containing content nodes. We collect the subimage nodes and 182 # section entries for them here to merge the content subnodes 183 # together and put the merged contents in the subimage node's 184 # 'data' property later. 185 entry = Entry.Create(self.section, node, etype='section') 186 entry.ReadNode() 187 self._fit_sections[rel_path] = entry 188 189 for subnode in node.subnodes: 190 if has_images and not (subnode.name.startswith('hash') or 191 subnode.name.startswith('signature')): 192 # This subnode is a content node not meant to appear in 193 # the FIT (e.g. "/images/kernel/u-boot"), so don't call 194 # fsw.add_node() or _AddNode() for it. 195 pass 196 elif subnode.name.startswith('@'): 197 if self._fdts: 198 # Generate notes for each FDT 199 for seq, fdt_fname in enumerate(self._fdts): 200 node_name = subnode.name[1:].replace('SEQ', 201 str(seq + 1)) 202 fname = tools.GetInputFilename(fdt_fname + '.dtb') 203 with fsw.add_node(node_name): 204 for pname, prop in subnode.props.items(): 205 val = prop.bytes.replace( 206 b'NAME', tools.ToBytes(fdt_fname)) 207 val = val.replace( 208 b'SEQ', tools.ToBytes(str(seq + 1))) 209 fsw.property(pname, val) 210 211 # Add data for 'fdt' nodes (but not 'config') 212 if depth == 1 and in_images: 213 fsw.property('data', 214 tools.ReadFile(fname)) 215 else: 216 if self._fdts is None: 217 if self._fit_list_prop: 218 self.Raise("Generator node requires '%s' entry argument" % 219 self._fit_list_prop.value) 220 else: 221 self.Raise("Generator node requires 'fit,fdt-list' property") 222 else: 223 with fsw.add_node(subnode.name): 224 _AddNode(base_node, depth + 1, subnode) 225 226 # Build a new tree with all nodes and properties starting from the 227 # entry node 228 fsw = libfdt.FdtSw() 229 fsw.finish_reservemap() 230 with fsw.add_node(''): 231 _AddNode(self._node, 0, self._node) 232 fdt = fsw.as_fdt() 233 234 # Pack this new FDT and scan it so we can add the data later 235 fdt.pack() 236 self._fdt = Fdt.FromData(fdt.as_bytearray()) 237 self._fdt.Scan() 238 239 def ObtainContents(self): 240 """Obtain the contents of the FIT 241 242 This adds the 'data' properties to the input ITB (Image-tree Binary) 243 then runs mkimage to process it. 244 """ 245 # self._BuildInput() either returns bytes or raises an exception. 246 data = self._BuildInput(self._fdt) 247 uniq = self.GetUniqueName() 248 input_fname = tools.GetOutputFilename('%s.itb' % uniq) 249 output_fname = tools.GetOutputFilename('%s.fit' % uniq) 250 tools.WriteFile(input_fname, data) 251 tools.WriteFile(output_fname, data) 252 253 args = [] 254 ext_offset = self._fit_props.get('fit,external-offset') 255 if ext_offset is not None: 256 args += ['-E', '-p', '%x' % fdt_util.fdt32_to_cpu(ext_offset.value)] 257 tools.Run('mkimage', '-t', '-F', output_fname, *args) 258 259 self.SetContents(tools.ReadFile(output_fname)) 260 return True 261 262 def _BuildInput(self, fdt): 263 """Finish the FIT by adding the 'data' properties to it 264 265 Arguments: 266 fdt: FIT to update 267 268 Returns: 269 New fdt contents (bytes) 270 """ 271 for path, section in self._fit_sections.items(): 272 node = fdt.GetNode(path) 273 # Entry_section.ObtainContents() either returns True or 274 # raises an exception. 275 section.ObtainContents() 276 section.Pack(0) 277 data = section.GetData() 278 node.AddData('data', data) 279 280 fdt.Sync(auto_resize=True) 281 data = fdt.GetContents() 282 return data 283 284 def CheckMissing(self, missing_list): 285 """Check if any entries in this FIT have missing external blobs 286 287 If there are missing blobs, the entries are added to the list 288 289 Args: 290 missing_list: List of Entry objects to be added to 291 """ 292 for path, section in self._fit_sections.items(): 293 section.CheckMissing(missing_list) 294 295 def SetAllowMissing(self, allow_missing): 296 for section in self._fit_sections.values(): 297 section.SetAllowMissing(allow_missing) 298