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