1# SPDX-License-Identifier: GPL-2.0+ 2# Copyright (c) 2016 Google, Inc 3# Written by Simon Glass <sjg@chromium.org> 4# 5# Class for an image, the output of binman 6# 7 8from collections import OrderedDict 9import fnmatch 10from operator import attrgetter 11import os 12import re 13import sys 14 15from binman.entry import Entry 16from binman.etype import fdtmap 17from binman.etype import image_header 18from binman.etype import section 19from dtoc import fdt 20from dtoc import fdt_util 21from u_boot_pylib import tools 22from u_boot_pylib import tout 23 24class Image(section.Entry_section): 25 """A Image, representing an output from binman 26 27 An image is comprised of a collection of entries each containing binary 28 data. The image size must be large enough to hold all of this data. 29 30 This class implements the various operations needed for images. 31 32 Attributes: 33 filename: Output filename for image 34 image_node: Name of node containing the description for this image 35 fdtmap_dtb: Fdt object for the fdtmap when loading from a file 36 fdtmap_data: Contents of the fdtmap when loading from a file 37 allow_repack: True to add properties to allow the image to be safely 38 repacked later 39 test_section_timeout: Use a zero timeout for section multi-threading 40 (for testing) 41 symlink: Name of symlink to image 42 43 Args: 44 copy_to_orig: Copy offset/size to orig_offset/orig_size after reading 45 from the device tree 46 test: True if this is being called from a test of Images. This this case 47 there is no device tree defining the structure of the section, so 48 we create a section manually. 49 ignore_missing: Ignore any missing entry arguments (i.e. don't raise an 50 exception). This should be used if the Image is being loaded from 51 a file rather than generated. In that case we obviously don't need 52 the entry arguments since the contents already exists. 53 use_expanded: True if we are updating the FDT wth entry offsets, etc. 54 and should use the expanded versions of the U-Boot entries. 55 Any entry type that includes a devicetree must put it in a 56 separate entry so that it will be updated. For example. 'u-boot' 57 normally just picks up 'u-boot.bin' which includes the 58 devicetree, but this is not updateable, since it comes into 59 binman as one piece and binman doesn't know that it is actually 60 an executable followed by a devicetree. Of course it could be 61 taught this, but then when reading an image (e.g. 'binman ls') 62 it may need to be able to split the devicetree out of the image 63 in order to determine the location of things. Instead we choose 64 to ignore 'u-boot-bin' in this case, and build it ourselves in 65 binman with 'u-boot-dtb.bin' and 'u-boot.dtb'. See 66 Entry_u_boot_expanded and Entry_blob_phase for details. 67 missing_etype: Use a default entry type ('blob') if the requested one 68 does not exist in binman. This is useful if an image was created by 69 binman a newer version of binman but we want to list it in an older 70 version which does not support all the entry types. 71 generate: If true, generator nodes are processed. If false they are 72 ignored which is useful when an existing image is read back from a 73 file. 74 """ 75 def __init__(self, name, node, copy_to_orig=True, test=False, 76 ignore_missing=False, use_expanded=False, missing_etype=False, 77 generate=True): 78 super().__init__(None, 'section', node, test=test) 79 self.copy_to_orig = copy_to_orig 80 self.name = name 81 self.image_name = name 82 self._filename = '%s.bin' % self.image_name 83 self.fdtmap_dtb = None 84 self.fdtmap_data = None 85 self.allow_repack = False 86 self._ignore_missing = ignore_missing 87 self.missing_etype = missing_etype 88 self.use_expanded = use_expanded 89 self.test_section_timeout = False 90 self.bintools = {} 91 self.generate = generate 92 if not test: 93 self.ReadNode() 94 95 def ReadNode(self): 96 super().ReadNode() 97 self.allow_repack = fdt_util.GetBool(self._node, 'allow-repack') 98 self._symlink = fdt_util.GetString(self._node, 'symlink') 99 100 @classmethod 101 def FromFile(cls, fname): 102 """Convert an image file into an Image for use in binman 103 104 Args: 105 fname: Filename of image file to read 106 107 Returns: 108 Image object on success 109 110 Raises: 111 ValueError if something goes wrong 112 """ 113 data = tools.read_file(fname) 114 size = len(data) 115 116 # First look for an image header 117 pos = image_header.LocateHeaderOffset(data) 118 if pos is None: 119 # Look for the FDT map 120 pos = fdtmap.LocateFdtmap(data) 121 if pos is None: 122 raise ValueError('Cannot find FDT map in image') 123 124 # We don't know the FDT size, so check its header first 125 probe_dtb = fdt.Fdt.FromData( 126 data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256]) 127 dtb_size = probe_dtb.GetFdtObj().totalsize() 128 fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN] 129 fdt_data = fdtmap_data[fdtmap.FDTMAP_HDR_LEN:] 130 out_fname = tools.get_output_filename('fdtmap.in.dtb') 131 tools.write_file(out_fname, fdt_data) 132 dtb = fdt.Fdt(out_fname) 133 dtb.Scan() 134 135 # Return an Image with the associated nodes 136 root = dtb.GetRoot() 137 image = Image('image', root, copy_to_orig=False, ignore_missing=True, 138 missing_etype=True, generate=False) 139 140 image.image_node = fdt_util.GetString(root, 'image-node', 'image') 141 image.fdtmap_dtb = dtb 142 image.fdtmap_data = fdtmap_data 143 image._data = data 144 image._filename = fname 145 image.image_name, _ = os.path.splitext(fname) 146 return image 147 148 def Raise(self, msg): 149 """Convenience function to raise an error referencing an image""" 150 raise ValueError("Image '%s': %s" % (self._node.path, msg)) 151 152 def PackEntries(self): 153 """Pack all entries into the image""" 154 super().Pack(0) 155 156 def SetImagePos(self): 157 # This first section in the image so it starts at 0 158 super().SetImagePos(0) 159 160 def ProcessEntryContents(self): 161 """Call the ProcessContents() method for each entry 162 163 This is intended to adjust the contents as needed by the entry type. 164 165 Returns: 166 True if the new data size is OK, False if expansion is needed 167 """ 168 return super().ProcessContents() 169 170 def WriteSymbols(self): 171 """Write symbol values into binary files for access at run time""" 172 super().WriteSymbols(self) 173 174 def BuildImage(self): 175 """Write the image to a file""" 176 fname = tools.get_output_filename(self._filename) 177 tout.info("Writing image to '%s'" % fname) 178 with open(fname, 'wb') as fd: 179 data = self.GetPaddedData() 180 fd.write(data) 181 tout.info("Wrote %#x bytes" % len(data)) 182 # Create symlink to file if symlink given 183 if self._symlink is not None: 184 sname = tools.get_output_filename(self._symlink) 185 os.symlink(fname, sname) 186 187 def WriteMap(self): 188 """Write a map of the image to a .map file 189 190 Returns: 191 Filename of map file written 192 """ 193 filename = '%s.map' % self.image_name 194 fname = tools.get_output_filename(filename) 195 with open(fname, 'w') as fd: 196 print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'), 197 file=fd) 198 super().WriteMap(fd, 0) 199 return fname 200 201 def BuildEntryList(self): 202 """List the files in an image 203 204 Returns: 205 List of entry.EntryInfo objects describing all entries in the image 206 """ 207 entries = [] 208 self.ListEntries(entries, 0) 209 return entries 210 211 def FindEntryPath(self, entry_path): 212 """Find an entry at a given path in the image 213 214 Args: 215 entry_path: Path to entry (e.g. /ro-section/u-boot') 216 217 Returns: 218 Entry object corresponding to that past 219 220 Raises: 221 ValueError if no entry found 222 """ 223 parts = entry_path.split('/') 224 entries = self.GetEntries() 225 parent = '/' 226 for part in parts: 227 entry = entries.get(part) 228 if not entry: 229 raise ValueError("Entry '%s' not found in '%s'" % 230 (part, parent)) 231 parent = entry.GetPath() 232 entries = entry.GetEntries() 233 return entry 234 235 def ReadData(self, decomp=True, alt_format=None): 236 tout.debug("Image '%s' ReadData(), size=%#x" % 237 (self.GetPath(), len(self._data))) 238 return self._data 239 240 def GetListEntries(self, entry_paths): 241 """List the entries in an image 242 243 This decodes the supplied image and returns a list of entries from that 244 image, preceded by a header. 245 246 Args: 247 entry_paths: List of paths to match (each can have wildcards). Only 248 entries whose names match one of these paths will be printed 249 250 Returns: 251 String error message if something went wrong, otherwise 252 3-Tuple: 253 List of EntryInfo objects 254 List of lines, each 255 List of text columns, each a string 256 List of widths of each column 257 """ 258 def _EntryToStrings(entry): 259 """Convert an entry to a list of strings, one for each column 260 261 Args: 262 entry: EntryInfo object containing information to output 263 264 Returns: 265 List of strings, one for each field in entry 266 """ 267 def _AppendHex(val): 268 """Append a hex value, or an empty string if val is None 269 270 Args: 271 val: Integer value, or None if none 272 """ 273 args.append('' if val is None else '>%x' % val) 274 275 args = [' ' * entry.indent + entry.name] 276 _AppendHex(entry.image_pos) 277 _AppendHex(entry.size) 278 args.append(entry.etype) 279 _AppendHex(entry.offset) 280 _AppendHex(entry.uncomp_size) 281 return args 282 283 def _DoLine(lines, line): 284 """Add a line to the output list 285 286 This adds a line (a list of columns) to the output list. It also updates 287 the widths[] array with the maximum width of each column 288 289 Args: 290 lines: List of lines to add to 291 line: List of strings, one for each column 292 """ 293 for i, item in enumerate(line): 294 widths[i] = max(widths[i], len(item)) 295 lines.append(line) 296 297 def _NameInPaths(fname, entry_paths): 298 """Check if a filename is in a list of wildcarded paths 299 300 Args: 301 fname: Filename to check 302 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*', 303 'section/u-boot']) 304 305 Returns: 306 True if any wildcard matches the filename (using Unix filename 307 pattern matching, not regular expressions) 308 False if not 309 """ 310 for path in entry_paths: 311 if fnmatch.fnmatch(fname, path): 312 return True 313 return False 314 315 entries = self.BuildEntryList() 316 317 # This is our list of lines. Each item in the list is a list of strings, one 318 # for each column 319 lines = [] 320 HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset', 321 'Uncomp-size'] 322 num_columns = len(HEADER) 323 324 # This records the width of each column, calculated as the maximum width of 325 # all the strings in that column 326 widths = [0] * num_columns 327 _DoLine(lines, HEADER) 328 329 # We won't print anything unless it has at least this indent. So at the 330 # start we will print nothing, unless a path matches (or there are no 331 # entry paths) 332 MAX_INDENT = 100 333 min_indent = MAX_INDENT 334 path_stack = [] 335 path = '' 336 indent = 0 337 selected_entries = [] 338 for entry in entries: 339 if entry.indent > indent: 340 path_stack.append(path) 341 elif entry.indent < indent: 342 path_stack.pop() 343 if path_stack: 344 path = path_stack[-1] + '/' + entry.name 345 indent = entry.indent 346 347 # If there are entry paths to match and we are not looking at a 348 # sub-entry of a previously matched entry, we need to check the path 349 if entry_paths and indent <= min_indent: 350 if _NameInPaths(path[1:], entry_paths): 351 # Print this entry and all sub-entries (=higher indent) 352 min_indent = indent 353 else: 354 # Don't print this entry, nor any following entries until we get 355 # a path match 356 min_indent = MAX_INDENT 357 continue 358 _DoLine(lines, _EntryToStrings(entry)) 359 selected_entries.append(entry) 360 return selected_entries, lines, widths 361 362 def LookupImageSymbol(self, sym_name, optional, msg, base_addr): 363 """Look up a symbol in an ELF file 364 365 Looks up a symbol in an ELF file. Only entry types which come from an 366 ELF image can be used by this function. 367 368 This searches through this image including all of its subsections. 369 370 At present the only entry properties supported are: 371 offset 372 image_pos - 'base_addr' is added if this is not an end-at-4gb image 373 size 374 375 Args: 376 sym_name: Symbol name in the ELF file to look up in the format 377 _binman_<entry>_prop_<property> where <entry> is the name of 378 the entry and <property> is the property to find (e.g. 379 _binman_u_boot_prop_offset). As a special case, you can append 380 _any to <entry> to have it search for any matching entry. E.g. 381 _binman_u_boot_any_prop_offset will match entries called u-boot, 382 u-boot-img and u-boot-nodtb) 383 optional: True if the symbol is optional. If False this function 384 will raise if the symbol is not found 385 msg: Message to display if an error occurs 386 base_addr: Base address of image. This is added to the returned 387 image_pos in most cases so that the returned position indicates 388 where the targeted entry/binary has actually been loaded. But 389 if end-at-4gb is used, this is not done, since the binary is 390 already assumed to be linked to the ROM position and using 391 execute-in-place (XIP). 392 393 Returns: 394 Value that should be assigned to that symbol, or None if it was 395 optional and not found 396 397 Raises: 398 ValueError if the symbol is invalid or not found, or references a 399 property which is not supported 400 """ 401 entries = OrderedDict() 402 entries_by_name = {} 403 self._CollectEntries(entries, entries_by_name, self) 404 return self.LookupSymbol(sym_name, optional, msg, base_addr, 405 entries_by_name) 406 407 def CollectBintools(self): 408 """Collect all the bintools used by this image 409 410 Returns: 411 Dict of bintools: 412 key: name of tool 413 value: Bintool object 414 """ 415 bintools = {} 416 super().AddBintools(bintools) 417 self.bintools = bintools 418 return bintools 419