1# SPDX-License-Identifier: GPL-2.0+ 2# Copyright 2018 Google, Inc 3# Written by Simon Glass <sjg@chromium.org> 4# 5# Holds and modifies the state information held by binman 6# 7 8import hashlib 9import re 10 11from dtoc import fdt 12import os 13from patman import tools 14from patman import tout 15 16# Records the device-tree files known to binman, keyed by entry type (e.g. 17# 'u-boot-spl-dtb'). These are the output FDT files, which can be updated by 18# binman. They have been copied to <xxx>.out files. 19# 20# key: entry type 21# value: tuple: 22# Fdt object 23# Filename 24# Entry object, or None if not known 25output_fdt_info = {} 26 27# Prefix to add to an fdtmap path to turn it into a path to the /binman node 28fdt_path_prefix = '' 29 30# Arguments passed to binman to provide arguments to entries 31entry_args = {} 32 33# True to use fake device-tree files for testing (see U_BOOT_DTB_DATA in 34# ftest.py) 35use_fake_dtb = False 36 37# The DTB which contains the full image information 38main_dtb = None 39 40# Allow entries to expand after they have been packed. This is detected and 41# forces a re-pack. If not allowed, any attempted expansion causes an error in 42# Entry.ProcessContentsUpdate() 43allow_entry_expansion = True 44 45# Don't allow entries to contract after they have been packed. Instead just 46# leave some wasted space. If allowed, this is detected and forces a re-pack, 47# but may result in entries that oscillate in size, thus causing a pack error. 48# An example is a compressed device tree where the original offset values 49# result in a larger compressed size than the new ones, but then after updating 50# to the new ones, the compressed size increases, etc. 51allow_entry_contraction = False 52 53def GetFdtForEtype(etype): 54 """Get the Fdt object for a particular device-tree entry 55 56 Binman keeps track of at least one device-tree file called u-boot.dtb but 57 can also have others (e.g. for SPL). This function looks up the given 58 entry and returns the associated Fdt object. 59 60 Args: 61 etype: Entry type of device tree (e.g. 'u-boot-dtb') 62 63 Returns: 64 Fdt object associated with the entry type 65 """ 66 value = output_fdt_info.get(etype); 67 if not value: 68 return None 69 return value[0] 70 71def GetFdtPath(etype): 72 """Get the full pathname of a particular Fdt object 73 74 Similar to GetFdtForEtype() but returns the pathname associated with the 75 Fdt. 76 77 Args: 78 etype: Entry type of device tree (e.g. 'u-boot-dtb') 79 80 Returns: 81 Full path name to the associated Fdt 82 """ 83 return output_fdt_info[etype][0]._fname 84 85def GetFdtContents(etype='u-boot-dtb'): 86 """Looks up the FDT pathname and contents 87 88 This is used to obtain the Fdt pathname and contents when needed by an 89 entry. It supports a 'fake' dtb, allowing tests to substitute test data for 90 the real dtb. 91 92 Args: 93 etype: Entry type to look up (e.g. 'u-boot.dtb'). 94 95 Returns: 96 tuple: 97 pathname to Fdt 98 Fdt data (as bytes) 99 """ 100 if etype not in output_fdt_info: 101 return None, None 102 if not use_fake_dtb: 103 pathname = GetFdtPath(etype) 104 data = GetFdtForEtype(etype).GetContents() 105 else: 106 fname = output_fdt_info[etype][1] 107 pathname = tools.GetInputFilename(fname) 108 data = tools.ReadFile(pathname) 109 return pathname, data 110 111def UpdateFdtContents(etype, data): 112 """Update the contents of a particular device tree 113 114 The device tree is updated and written back to its file. This affects what 115 is returned from future called to GetFdtContents(), etc. 116 117 Args: 118 etype: Entry type (e.g. 'u-boot-dtb') 119 data: Data to replace the DTB with 120 """ 121 dtb, fname, entry = output_fdt_info[etype] 122 dtb_fname = dtb.GetFilename() 123 tools.WriteFile(dtb_fname, data) 124 dtb = fdt.FdtScan(dtb_fname) 125 output_fdt_info[etype] = [dtb, fname, entry] 126 127def SetEntryArgs(args): 128 """Set the value of the entry args 129 130 This sets up the entry_args dict which is used to supply entry arguments to 131 entries. 132 133 Args: 134 args: List of entry arguments, each in the format "name=value" 135 """ 136 global entry_args 137 138 entry_args = {} 139 if args: 140 for arg in args: 141 m = re.match('([^=]*)=(.*)', arg) 142 if not m: 143 raise ValueError("Invalid entry arguemnt '%s'" % arg) 144 entry_args[m.group(1)] = m.group(2) 145 146def GetEntryArg(name): 147 """Get the value of an entry argument 148 149 Args: 150 name: Name of argument to retrieve 151 152 Returns: 153 String value of argument 154 """ 155 return entry_args.get(name) 156 157def Prepare(images, dtb): 158 """Get device tree files ready for use 159 160 This sets up a set of device tree files that can be retrieved by 161 GetAllFdts(). This includes U-Boot proper and any SPL device trees. 162 163 Args: 164 images: List of images being used 165 dtb: Main dtb 166 """ 167 global output_fdt_info, main_dtb, fdt_path_prefix 168 # Import these here in case libfdt.py is not available, in which case 169 # the above help option still works. 170 from dtoc import fdt 171 from dtoc import fdt_util 172 173 # If we are updating the DTBs we need to put these updated versions 174 # where Entry_blob_dtb can find them. We can ignore 'u-boot.dtb' 175 # since it is assumed to be the one passed in with options.dt, and 176 # was handled just above. 177 main_dtb = dtb 178 output_fdt_info.clear() 179 fdt_path_prefix = '' 180 output_fdt_info['u-boot-dtb'] = [dtb, 'u-boot.dtb', None] 181 output_fdt_info['u-boot-spl-dtb'] = [dtb, 'spl/u-boot-spl.dtb', None] 182 output_fdt_info['u-boot-tpl-dtb'] = [dtb, 'tpl/u-boot-tpl.dtb', None] 183 if not use_fake_dtb: 184 fdt_set = {} 185 for image in images.values(): 186 fdt_set.update(image.GetFdts()) 187 for etype, other in fdt_set.items(): 188 entry, other_fname = other 189 infile = tools.GetInputFilename(other_fname) 190 other_fname_dtb = fdt_util.EnsureCompiled(infile) 191 out_fname = tools.GetOutputFilename('%s.out' % 192 os.path.split(other_fname)[1]) 193 tools.WriteFile(out_fname, tools.ReadFile(other_fname_dtb)) 194 other_dtb = fdt.FdtScan(out_fname) 195 output_fdt_info[etype] = [other_dtb, out_fname, entry] 196 197def PrepareFromLoadedData(image): 198 """Get device tree files ready for use with a loaded image 199 200 Loaded images are different from images that are being created by binman, 201 since there is generally already an fdtmap and we read the description from 202 that. This provides the position and size of every entry in the image with 203 no calculation required. 204 205 This function uses the same output_fdt_info[] as Prepare(). It finds the 206 device tree files, adds a reference to the fdtmap and sets the FDT path 207 prefix to translate from the fdtmap (where the root node is the image node) 208 to the normal device tree (where the image node is under a /binman node). 209 210 Args: 211 images: List of images being used 212 """ 213 global output_fdt_info, main_dtb, fdt_path_prefix 214 215 tout.Info('Preparing device trees') 216 output_fdt_info.clear() 217 fdt_path_prefix = '' 218 output_fdt_info['fdtmap'] = [image.fdtmap_dtb, 'u-boot.dtb', None] 219 main_dtb = None 220 tout.Info(" Found device tree type 'fdtmap' '%s'" % image.fdtmap_dtb.name) 221 for etype, value in image.GetFdts().items(): 222 entry, fname = value 223 out_fname = tools.GetOutputFilename('%s.dtb' % entry.etype) 224 tout.Info(" Found device tree type '%s' at '%s' path '%s'" % 225 (etype, out_fname, entry.GetPath())) 226 entry._filename = entry.GetDefaultFilename() 227 data = entry.ReadData() 228 229 tools.WriteFile(out_fname, data) 230 dtb = fdt.Fdt(out_fname) 231 dtb.Scan() 232 image_node = dtb.GetNode('/binman') 233 if 'multiple-images' in image_node.props: 234 image_node = dtb.GetNode('/binman/%s' % image.image_node) 235 fdt_path_prefix = image_node.path 236 output_fdt_info[etype] = [dtb, None, entry] 237 tout.Info(" FDT path prefix '%s'" % fdt_path_prefix) 238 239 240def GetAllFdts(): 241 """Yield all device tree files being used by binman 242 243 Yields: 244 Device trees being used (U-Boot proper, SPL, TPL) 245 """ 246 if main_dtb: 247 yield main_dtb 248 for etype in output_fdt_info: 249 dtb = output_fdt_info[etype][0] 250 if dtb != main_dtb: 251 yield dtb 252 253def GetUpdateNodes(node, for_repack=False): 254 """Yield all the nodes that need to be updated in all device trees 255 256 The property referenced by this node is added to any device trees which 257 have the given node. Due to removable of unwanted notes, SPL and TPL may 258 not have this node. 259 260 Args: 261 node: Node object in the main device tree to look up 262 for_repack: True if we want only nodes which need 'repack' properties 263 added to them (e.g. 'orig-offset'), False to return all nodes. We 264 don't add repack properties to SPL/TPL device trees. 265 266 Yields: 267 Node objects in each device tree that is in use (U-Boot proper, which 268 is node, SPL and TPL) 269 """ 270 yield node 271 for dtb, fname, entry in output_fdt_info.values(): 272 if dtb != node.GetFdt(): 273 if for_repack and entry.etype != 'u-boot-dtb': 274 continue 275 other_node = dtb.GetNode(fdt_path_prefix + node.path) 276 #print(' try', fdt_path_prefix + node.path, other_node) 277 if other_node: 278 yield other_node 279 280def AddZeroProp(node, prop, for_repack=False): 281 """Add a new property to affected device trees with an integer value of 0. 282 283 Args: 284 prop_name: Name of property 285 for_repack: True is this property is only needed for repacking 286 """ 287 for n in GetUpdateNodes(node, for_repack): 288 n.AddZeroProp(prop) 289 290def AddSubnode(node, name): 291 """Add a new subnode to a node in affected device trees 292 293 Args: 294 node: Node to add to 295 name: name of node to add 296 297 Returns: 298 New subnode that was created in main tree 299 """ 300 first = None 301 for n in GetUpdateNodes(node): 302 subnode = n.AddSubnode(name) 303 if not first: 304 first = subnode 305 return first 306 307def AddString(node, prop, value): 308 """Add a new string property to affected device trees 309 310 Args: 311 prop_name: Name of property 312 value: String value (which will be \0-terminated in the DT) 313 """ 314 for n in GetUpdateNodes(node): 315 n.AddString(prop, value) 316 317def AddInt(node, prop, value): 318 """Add a new string property to affected device trees 319 320 Args: 321 prop_name: Name of property 322 val: Integer value of property 323 """ 324 for n in GetUpdateNodes(node): 325 n.AddInt(prop, value) 326 327def SetInt(node, prop, value, for_repack=False): 328 """Update an integer property in affected device trees with an integer value 329 330 This is not allowed to change the size of the FDT. 331 332 Args: 333 prop_name: Name of property 334 for_repack: True is this property is only needed for repacking 335 """ 336 for n in GetUpdateNodes(node, for_repack): 337 tout.Detail("File %s: Update node '%s' prop '%s' to %#x" % 338 (n.GetFdt().name, n.path, prop, value)) 339 n.SetInt(prop, value) 340 341def CheckAddHashProp(node): 342 hash_node = node.FindNode('hash') 343 if hash_node: 344 algo = hash_node.props.get('algo') 345 if not algo: 346 return "Missing 'algo' property for hash node" 347 if algo.value == 'sha256': 348 size = 32 349 else: 350 return "Unknown hash algorithm '%s'" % algo 351 for n in GetUpdateNodes(hash_node): 352 n.AddEmptyProp('value', size) 353 354def CheckSetHashValue(node, get_data_func): 355 hash_node = node.FindNode('hash') 356 if hash_node: 357 algo = hash_node.props.get('algo').value 358 if algo == 'sha256': 359 m = hashlib.sha256() 360 m.update(get_data_func()) 361 data = m.digest() 362 for n in GetUpdateNodes(hash_node): 363 n.SetData('value', data) 364 365def SetAllowEntryExpansion(allow): 366 """Set whether post-pack expansion of entries is allowed 367 368 Args: 369 allow: True to allow expansion, False to raise an exception 370 """ 371 global allow_entry_expansion 372 373 allow_entry_expansion = allow 374 375def AllowEntryExpansion(): 376 """Check whether post-pack expansion of entries is allowed 377 378 Returns: 379 True if expansion should be allowed, False if an exception should be 380 raised 381 """ 382 return allow_entry_expansion 383 384def SetAllowEntryContraction(allow): 385 """Set whether post-pack contraction of entries is allowed 386 387 Args: 388 allow: True to allow contraction, False to raise an exception 389 """ 390 global allow_entry_contraction 391 392 allow_entry_contraction = allow 393 394def AllowEntryContraction(): 395 """Check whether post-pack contraction of entries is allowed 396 397 Returns: 398 True if contraction should be allowed, False if an exception should be 399 raised 400 """ 401 return allow_entry_contraction 402