xref: /rk3399_rockchip-uboot/tools/binman/image.py (revision 36737f22b78a475c6bbc8a0467b51e4d95b52a7d)
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