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