Simon Glass | 2574ef6 | 2016-11-25 20:15:51 -0700 | [diff] [blame] | 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 | |
Simon Glass | 4ca8e04 | 2017-11-13 18:55:01 -0700 | [diff] [blame] | 9 | from __future__ import print_function |
| 10 | |
Simon Glass | 2574ef6 | 2016-11-25 20:15:51 -0700 | [diff] [blame] | 11 | from collections import OrderedDict |
| 12 | from operator import attrgetter |
Simon Glass | 4ca8e04 | 2017-11-13 18:55:01 -0700 | [diff] [blame] | 13 | import re |
| 14 | import sys |
Simon Glass | 2574ef6 | 2016-11-25 20:15:51 -0700 | [diff] [blame] | 15 | |
Simon Glass | 2574ef6 | 2016-11-25 20:15:51 -0700 | [diff] [blame] | 16 | import fdt_util |
| 17 | import tools |
| 18 | |
| 19 | class 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 | """ |
Simon Glass | 4ca8e04 | 2017-11-13 18:55:01 -0700 | [diff] [blame] | 52 | def __init__(self, name, node, test=False): |
Simon Glass | b339326 | 2017-11-12 21:52:20 -0700 | [diff] [blame] | 53 | global entry |
| 54 | global Entry |
| 55 | import entry |
| 56 | from entry import Entry |
| 57 | |
Simon Glass | 2574ef6 | 2016-11-25 20:15:51 -0700 | [diff] [blame] | 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 | |
Simon Glass | 4ca8e04 | 2017-11-13 18:55:01 -0700 | [diff] [blame] | 71 | if not test: |
| 72 | self._ReadNode() |
| 73 | self._ReadEntries() |
Simon Glass | 2574ef6 | 2016-11-25 20:15:51 -0700 | [diff] [blame] | 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 | |
Simon Glass | 4ca8e04 | 2017-11-13 18:55:01 -0700 | [diff] [blame] | 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 | |
Simon Glass | 2574ef6 | 2016-11-25 20:15:51 -0700 | [diff] [blame] | 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 | |
Simon Glass | 4ca8e04 | 2017-11-13 18:55:01 -0700 | [diff] [blame] | 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 | |
Simon Glass | 2574ef6 | 2016-11-25 20:15:51 -0700 | [diff] [blame] | 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) |
Simon Glass | 4ca8e04 | 2017-11-13 18:55:01 -0700 | [diff] [blame] | 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)) |