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