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