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