xref: /OK3568_Linux_fs/u-boot/tools/binman/image.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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