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