1# Copyright (c) 2016 Google, Inc 2# Written by Simon Glass <sjg@chromium.org> 3# 4# SPDX-License-Identifier: GPL-2.0+ 5# 6# Class for an image, the output of binman 7# 8 9from __future__ import print_function 10 11from collections import OrderedDict 12from operator import attrgetter 13import re 14import sys 15 16import fdt_util 17import tools 18 19class Image: 20 """A Image, representing an output from binman 21 22 An image is comprised of a collection of entries each containing binary 23 data. The image size must be large enough to hold all of this data. 24 25 This class implements the various operations needed for images. 26 27 Atrtributes: 28 _node: Node object that contains the image definition in device tree 29 _name: Image name 30 _size: Image size in bytes, or None if not known yet 31 _align_size: Image size alignment, or None 32 _pad_before: Number of bytes before the first entry starts. This 33 effectively changes the place where entry position 0 starts 34 _pad_after: Number of bytes after the last entry ends. The last 35 entry will finish on or before this boundary 36 _pad_byte: Byte to use to pad the image where there is no entry 37 _filename: Output filename for image 38 _sort: True if entries should be sorted by position, False if they 39 must be in-order in the device tree description 40 _skip_at_start: Number of bytes before the first entry starts. These 41 effecively adjust the starting position of entries. For example, 42 if _pad_before is 16, then the first entry would start at 16. 43 An entry with pos = 20 would in fact be written at position 4 44 in the image file. 45 _end_4gb: Indicates that the image ends at the 4GB boundary. This is 46 used for x86 images, which want to use positions such that a 47 memory address (like 0xff800000) is the first entry position. 48 This causes _skip_at_start to be set to the starting memory 49 address. 50 _entries: OrderedDict() of entries 51 """ 52 def __init__(self, name, node, test=False): 53 global entry 54 global Entry 55 import entry 56 from entry import Entry 57 58 self._node = node 59 self._name = name 60 self._size = None 61 self._align_size = None 62 self._pad_before = 0 63 self._pad_after = 0 64 self._pad_byte = 0 65 self._filename = '%s.bin' % self._name 66 self._sort = False 67 self._skip_at_start = 0 68 self._end_4gb = False 69 self._entries = OrderedDict() 70 71 if not test: 72 self._ReadNode() 73 self._ReadEntries() 74 75 def _ReadNode(self): 76 """Read properties from the image node""" 77 self._size = fdt_util.GetInt(self._node, 'size') 78 self._align_size = fdt_util.GetInt(self._node, 'align-size') 79 if tools.NotPowerOfTwo(self._align_size): 80 self._Raise("Alignment size %s must be a power of two" % 81 self._align_size) 82 self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0) 83 self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0) 84 self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0) 85 filename = fdt_util.GetString(self._node, 'filename') 86 if filename: 87 self._filename = filename 88 self._sort = fdt_util.GetBool(self._node, 'sort-by-pos') 89 self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb') 90 if self._end_4gb and not self._size: 91 self._Raise("Image size must be provided when using end-at-4gb") 92 if self._end_4gb: 93 self._skip_at_start = 0x100000000 - self._size 94 95 def CheckSize(self): 96 """Check that the image contents does not exceed its size, etc.""" 97 contents_size = 0 98 for entry in self._entries.values(): 99 contents_size = max(contents_size, entry.pos + entry.size) 100 101 contents_size -= self._skip_at_start 102 103 size = self._size 104 if not size: 105 size = self._pad_before + contents_size + self._pad_after 106 size = tools.Align(size, self._align_size) 107 108 if self._size and contents_size > self._size: 109 self._Raise("contents size %#x (%d) exceeds image size %#x (%d)" % 110 (contents_size, contents_size, self._size, self._size)) 111 if not self._size: 112 self._size = size 113 if self._size != tools.Align(self._size, self._align_size): 114 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" % 115 (self._size, self._size, self._align_size, self._align_size)) 116 117 def _Raise(self, msg): 118 """Raises an error for this image 119 120 Args: 121 msg: Error message to use in the raise string 122 Raises: 123 ValueError() 124 """ 125 raise ValueError("Image '%s': %s" % (self._node.path, msg)) 126 127 def GetPath(self): 128 """Get the path of an image (in the FDT) 129 130 Returns: 131 Full path of the node for this image 132 """ 133 return self._node.path 134 135 def _ReadEntries(self): 136 for node in self._node.subnodes: 137 self._entries[node.name] = Entry.Create(self, node) 138 139 def FindEntryType(self, etype): 140 """Find an entry type in the image 141 142 Args: 143 etype: Entry type to find 144 Returns: 145 entry matching that type, or None if not found 146 """ 147 for entry in self._entries.values(): 148 if entry.etype == etype: 149 return entry 150 return None 151 152 def GetEntryContents(self): 153 """Call ObtainContents() for each entry 154 155 This calls each entry's ObtainContents() a few times until they all 156 return True. We stop calling an entry's function once it returns 157 True. This allows the contents of one entry to depend on another. 158 159 After 3 rounds we give up since it's likely an error. 160 """ 161 todo = self._entries.values() 162 for passnum in range(3): 163 next_todo = [] 164 for entry in todo: 165 if not entry.ObtainContents(): 166 next_todo.append(entry) 167 todo = next_todo 168 if not todo: 169 break 170 171 def _SetEntryPosSize(self, name, pos, size): 172 """Set the position and size of an entry 173 174 Args: 175 name: Entry name to update 176 pos: New position 177 size: New size 178 """ 179 entry = self._entries.get(name) 180 if not entry: 181 self._Raise("Unable to set pos/size for unknown entry '%s'" % name) 182 entry.SetPositionSize(self._skip_at_start + pos, size) 183 184 def GetEntryPositions(self): 185 """Handle entries that want to set the position/size of other entries 186 187 This calls each entry's GetPositions() method. If it returns a list 188 of entries to update, it updates them. 189 """ 190 for entry in self._entries.values(): 191 pos_dict = entry.GetPositions() 192 for name, info in pos_dict.iteritems(): 193 self._SetEntryPosSize(name, *info) 194 195 def PackEntries(self): 196 """Pack all entries into the image""" 197 pos = self._skip_at_start 198 for entry in self._entries.values(): 199 pos = entry.Pack(pos) 200 201 def _SortEntries(self): 202 """Sort entries by position""" 203 entries = sorted(self._entries.values(), key=lambda entry: entry.pos) 204 self._entries.clear() 205 for entry in entries: 206 self._entries[entry._node.name] = entry 207 208 def CheckEntries(self): 209 """Check that entries do not overlap or extend outside the image""" 210 if self._sort: 211 self._SortEntries() 212 pos = 0 213 prev_name = 'None' 214 for entry in self._entries.values(): 215 if (entry.pos < self._skip_at_start or 216 entry.pos >= self._skip_at_start + self._size): 217 entry.Raise("Position %#x (%d) is outside the image starting " 218 "at %#x (%d)" % 219 (entry.pos, entry.pos, self._skip_at_start, 220 self._skip_at_start)) 221 if entry.pos < pos: 222 entry.Raise("Position %#x (%d) overlaps with previous entry '%s' " 223 "ending at %#x (%d)" % 224 (entry.pos, entry.pos, prev_name, pos, pos)) 225 pos = entry.pos + entry.size 226 prev_name = entry.GetPath() 227 228 def ProcessEntryContents(self): 229 """Call the ProcessContents() method for each entry 230 231 This is intended to adjust the contents as needed by the entry type. 232 """ 233 for entry in self._entries.values(): 234 entry.ProcessContents() 235 236 def WriteSymbols(self): 237 """Write symbol values into binary files for access at run time""" 238 for entry in self._entries.values(): 239 entry.WriteSymbols(self) 240 241 def BuildImage(self): 242 """Write the image to a file""" 243 fname = tools.GetOutputFilename(self._filename) 244 with open(fname, 'wb') as fd: 245 fd.write(chr(self._pad_byte) * self._size) 246 247 for entry in self._entries.values(): 248 data = entry.GetData() 249 fd.seek(self._pad_before + entry.pos - self._skip_at_start) 250 fd.write(data) 251 252 def LookupSymbol(self, sym_name, optional, msg): 253 """Look up a symbol in an ELF file 254 255 Looks up a symbol in an ELF file. Only entry types which come from an 256 ELF image can be used by this function. 257 258 At present the only entry property supported is pos. 259 260 Args: 261 sym_name: Symbol name in the ELF file to look up in the format 262 _binman_<entry>_prop_<property> where <entry> is the name of 263 the entry and <property> is the property to find (e.g. 264 _binman_u_boot_prop_pos). As a special case, you can append 265 _any to <entry> to have it search for any matching entry. E.g. 266 _binman_u_boot_any_prop_pos will match entries called u-boot, 267 u-boot-img and u-boot-nodtb) 268 optional: True if the symbol is optional. If False this function 269 will raise if the symbol is not found 270 msg: Message to display if an error occurs 271 272 Returns: 273 Value that should be assigned to that symbol, or None if it was 274 optional and not found 275 276 Raises: 277 ValueError if the symbol is invalid or not found, or references a 278 property which is not supported 279 """ 280 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name) 281 if not m: 282 raise ValueError("%s: Symbol '%s' has invalid format" % 283 (msg, sym_name)) 284 entry_name, prop_name = m.groups() 285 entry_name = entry_name.replace('_', '-') 286 entry = self._entries.get(entry_name) 287 if not entry: 288 if entry_name.endswith('-any'): 289 root = entry_name[:-4] 290 for name in self._entries: 291 if name.startswith(root): 292 rest = name[len(root):] 293 if rest in ['', '-img', '-nodtb']: 294 entry = self._entries[name] 295 if not entry: 296 err = ("%s: Entry '%s' not found in list (%s)" % 297 (msg, entry_name, ','.join(self._entries.keys()))) 298 if optional: 299 print('Warning: %s' % err, file=sys.stderr) 300 return None 301 raise ValueError(err) 302 if prop_name == 'pos': 303 return entry.pos 304 else: 305 raise ValueError("%s: No such property '%s'" % (msg, prop_name)) 306