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