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