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 collections import OrderedDict 10from operator import attrgetter 11 12import fdt_util 13import tools 14 15class Image: 16 """A Image, representing an output from binman 17 18 An image is comprised of a collection of entries each containing binary 19 data. The image size must be large enough to hold all of this data. 20 21 This class implements the various operations needed for images. 22 23 Atrtributes: 24 _node: Node object that contains the image definition in device tree 25 _name: Image name 26 _size: Image size in bytes, or None if not known yet 27 _align_size: Image size alignment, or None 28 _pad_before: Number of bytes before the first entry starts. This 29 effectively changes the place where entry position 0 starts 30 _pad_after: Number of bytes after the last entry ends. The last 31 entry will finish on or before this boundary 32 _pad_byte: Byte to use to pad the image where there is no entry 33 _filename: Output filename for image 34 _sort: True if entries should be sorted by position, False if they 35 must be in-order in the device tree description 36 _skip_at_start: Number of bytes before the first entry starts. These 37 effecively adjust the starting position of entries. For example, 38 if _pad_before is 16, then the first entry would start at 16. 39 An entry with pos = 20 would in fact be written at position 4 40 in the image file. 41 _end_4gb: Indicates that the image ends at the 4GB boundary. This is 42 used for x86 images, which want to use positions such that a 43 memory address (like 0xff800000) is the first entry position. 44 This causes _skip_at_start to be set to the starting memory 45 address. 46 _entries: OrderedDict() of entries 47 """ 48 def __init__(self, name, node): 49 global entry 50 global Entry 51 import entry 52 from entry import Entry 53 54 self._node = node 55 self._name = name 56 self._size = None 57 self._align_size = None 58 self._pad_before = 0 59 self._pad_after = 0 60 self._pad_byte = 0 61 self._filename = '%s.bin' % self._name 62 self._sort = False 63 self._skip_at_start = 0 64 self._end_4gb = False 65 self._entries = OrderedDict() 66 67 self._ReadNode() 68 self._ReadEntries() 69 70 def _ReadNode(self): 71 """Read properties from the image node""" 72 self._size = fdt_util.GetInt(self._node, 'size') 73 self._align_size = fdt_util.GetInt(self._node, 'align-size') 74 if tools.NotPowerOfTwo(self._align_size): 75 self._Raise("Alignment size %s must be a power of two" % 76 self._align_size) 77 self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0) 78 self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0) 79 self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0) 80 filename = fdt_util.GetString(self._node, 'filename') 81 if filename: 82 self._filename = filename 83 self._sort = fdt_util.GetBool(self._node, 'sort-by-pos') 84 self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb') 85 if self._end_4gb and not self._size: 86 self._Raise("Image size must be provided when using end-at-4gb") 87 if self._end_4gb: 88 self._skip_at_start = 0x100000000 - self._size 89 90 def CheckSize(self): 91 """Check that the image contents does not exceed its size, etc.""" 92 contents_size = 0 93 for entry in self._entries.values(): 94 contents_size = max(contents_size, entry.pos + entry.size) 95 96 contents_size -= self._skip_at_start 97 98 size = self._size 99 if not size: 100 size = self._pad_before + contents_size + self._pad_after 101 size = tools.Align(size, self._align_size) 102 103 if self._size and contents_size > self._size: 104 self._Raise("contents size %#x (%d) exceeds image size %#x (%d)" % 105 (contents_size, contents_size, self._size, self._size)) 106 if not self._size: 107 self._size = size 108 if self._size != tools.Align(self._size, self._align_size): 109 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" % 110 (self._size, self._size, self._align_size, self._align_size)) 111 112 def _Raise(self, msg): 113 """Raises an error for this image 114 115 Args: 116 msg: Error message to use in the raise string 117 Raises: 118 ValueError() 119 """ 120 raise ValueError("Image '%s': %s" % (self._node.path, msg)) 121 122 def _ReadEntries(self): 123 for node in self._node.subnodes: 124 self._entries[node.name] = Entry.Create(self, node) 125 126 def FindEntryType(self, etype): 127 """Find an entry type in the image 128 129 Args: 130 etype: Entry type to find 131 Returns: 132 entry matching that type, or None if not found 133 """ 134 for entry in self._entries.values(): 135 if entry.etype == etype: 136 return entry 137 return None 138 139 def GetEntryContents(self): 140 """Call ObtainContents() for each entry 141 142 This calls each entry's ObtainContents() a few times until they all 143 return True. We stop calling an entry's function once it returns 144 True. This allows the contents of one entry to depend on another. 145 146 After 3 rounds we give up since it's likely an error. 147 """ 148 todo = self._entries.values() 149 for passnum in range(3): 150 next_todo = [] 151 for entry in todo: 152 if not entry.ObtainContents(): 153 next_todo.append(entry) 154 todo = next_todo 155 if not todo: 156 break 157 158 def _SetEntryPosSize(self, name, pos, size): 159 """Set the position and size of an entry 160 161 Args: 162 name: Entry name to update 163 pos: New position 164 size: New size 165 """ 166 entry = self._entries.get(name) 167 if not entry: 168 self._Raise("Unable to set pos/size for unknown entry '%s'" % name) 169 entry.SetPositionSize(self._skip_at_start + pos, size) 170 171 def GetEntryPositions(self): 172 """Handle entries that want to set the position/size of other entries 173 174 This calls each entry's GetPositions() method. If it returns a list 175 of entries to update, it updates them. 176 """ 177 for entry in self._entries.values(): 178 pos_dict = entry.GetPositions() 179 for name, info in pos_dict.iteritems(): 180 self._SetEntryPosSize(name, *info) 181 182 def PackEntries(self): 183 """Pack all entries into the image""" 184 pos = self._skip_at_start 185 for entry in self._entries.values(): 186 pos = entry.Pack(pos) 187 188 def _SortEntries(self): 189 """Sort entries by position""" 190 entries = sorted(self._entries.values(), key=lambda entry: entry.pos) 191 self._entries.clear() 192 for entry in entries: 193 self._entries[entry._node.name] = entry 194 195 def CheckEntries(self): 196 """Check that entries do not overlap or extend outside the image""" 197 if self._sort: 198 self._SortEntries() 199 pos = 0 200 prev_name = 'None' 201 for entry in self._entries.values(): 202 if (entry.pos < self._skip_at_start or 203 entry.pos >= self._skip_at_start + self._size): 204 entry.Raise("Position %#x (%d) is outside the image starting " 205 "at %#x (%d)" % 206 (entry.pos, entry.pos, self._skip_at_start, 207 self._skip_at_start)) 208 if entry.pos < pos: 209 entry.Raise("Position %#x (%d) overlaps with previous entry '%s' " 210 "ending at %#x (%d)" % 211 (entry.pos, entry.pos, prev_name, pos, pos)) 212 pos = entry.pos + entry.size 213 prev_name = entry.GetPath() 214 215 def ProcessEntryContents(self): 216 """Call the ProcessContents() method for each entry 217 218 This is intended to adjust the contents as needed by the entry type. 219 """ 220 for entry in self._entries.values(): 221 entry.ProcessContents() 222 223 def BuildImage(self): 224 """Write the image to a file""" 225 fname = tools.GetOutputFilename(self._filename) 226 with open(fname, 'wb') as fd: 227 fd.write(chr(self._pad_byte) * self._size) 228 229 for entry in self._entries.values(): 230 data = entry.GetData() 231 fd.seek(self._pad_before + entry.pos - self._skip_at_start) 232 fd.write(data) 233