1# SPDX-License-Identifier: GPL-2.0+
2# Copyright (c) 2016 Google, Inc
3# Written by Simon Glass <sjg@chromium.org>
4#
5# Entry-type module for producing an image using mkimage
6#
7
8from collections import OrderedDict
9
10from binman.entry import Entry
11from binman.etype.section import Entry_section
12from dtoc import fdt_util
13from u_boot_pylib import tools
14
15class Entry_mkimage(Entry_section):
16    """Binary produced by mkimage
17
18    Properties / Entry arguments:
19        - args: Arguments to pass
20        - data-to-imagename: Indicates that the -d data should be passed in as
21          the image name also (-n)
22        - multiple-data-files: boolean to tell binman to pass all files as
23          datafiles to mkimage instead of creating a temporary file the result
24          of datafiles concatenation
25        - filename: filename of output binary generated by mkimage
26
27    The data passed to mkimage via the -d flag is collected from subnodes of the
28    mkimage node, e.g.::
29
30        mkimage {
31            filename = "imximage.bin";
32            args = "-n test -T imximage";
33
34            u-boot-spl {
35            };
36        };
37
38    This calls mkimage to create an imximage with `u-boot-spl.bin` as the data
39    file, with mkimage being called like this::
40
41        mkimage -d <data_file> -n test -T imximage <output_file>
42
43    The output from mkimage then becomes part of the image produced by
44    binman but also is written into `imximage.bin` file. If you need to put
45    multiple things in the data file, you can use a section, or just multiple
46    subnodes like this::
47
48        mkimage {
49            args = "-n test -T imximage";
50
51            u-boot-spl {
52            };
53
54            u-boot-tpl {
55            };
56        };
57
58    Note that binman places the contents (here SPL and TPL) into a single file
59    and passes that to mkimage using the -d option.
60
61    To pass all datafiles untouched to mkimage::
62
63        mkimage {
64                args = "-n rk3399 -T rkspi";
65                multiple-data-files;
66
67                u-boot-tpl {
68                };
69
70                u-boot-spl {
71                };
72        };
73
74    This calls mkimage to create a Rockchip RK3399-specific first stage
75    bootloader, made of TPL+SPL. Since this first stage bootloader requires to
76    align the TPL and SPL but also some weird hacks that is handled by mkimage
77    directly, binman is told to not perform the concatenation of datafiles prior
78    to passing the data to mkimage.
79
80    To use CONFIG options in the arguments, use a string list instead, as in
81    this example which also produces four arguments::
82
83        mkimage {
84            args = "-n", CONFIG_SYS_SOC, "-T imximage";
85
86            u-boot-spl {
87            };
88        };
89
90    If you need to pass the input data in with the -n argument as well, then use
91    the 'data-to-imagename' property::
92
93        mkimage {
94            args = "-T imximage";
95            data-to-imagename;
96
97            u-boot-spl {
98            };
99        };
100
101    That will pass the data to mkimage both as the data file (with -d) and as
102    the image name (with -n). In both cases, a filename is passed as the
103    argument, with the actual data being in that file.
104
105    If need to pass different data in with -n, then use an `imagename` subnode::
106
107        mkimage {
108            args = "-T imximage";
109
110            imagename {
111                blob {
112                    filename = "spl/u-boot-spl.cfgout"
113                };
114            };
115
116            u-boot-spl {
117            };
118        };
119
120    This will pass in u-boot-spl as the input data and the .cfgout file as the
121    -n data.
122    """
123    def __init__(self, section, etype, node):
124        super().__init__(section, etype, node)
125        self._imagename = None
126        self._multiple_data_files = False
127
128    def ReadNode(self):
129        super().ReadNode()
130        self._multiple_data_files = fdt_util.GetBool(self._node,
131                                                     'multiple-data-files')
132        self._args = fdt_util.GetArgs(self._node, 'args')
133        self._data_to_imagename = fdt_util.GetBool(self._node,
134                                                   'data-to-imagename')
135        if self._data_to_imagename and self._node.FindNode('imagename'):
136            self.Raise('Cannot use both imagename node and data-to-imagename')
137
138    def ReadEntries(self):
139        """Read the subnodes to find out what should go in this image"""
140        for node in self._node.subnodes:
141            if self.IsSpecialSubnode(node):
142                continue
143            entry = Entry.Create(self, node,
144                                 expanded=self.GetImage().use_expanded,
145                                 missing_etype=self.GetImage().missing_etype)
146            entry.ReadNode()
147            entry.SetPrefix(self._name_prefix)
148            if entry.name == 'imagename':
149                self._imagename = entry
150            else:
151                self._entries[entry.name] = entry
152
153    def BuildSectionData(self, required):
154        """Build mkimage entry contents
155
156        Runs mkimage to build the entry contents
157
158        Args:
159            required (bool): True if the data must be present, False if it is OK
160                to return None
161
162        Returns:
163            bytes: Contents of the section
164        """
165        # Use a non-zero size for any fake files to keep mkimage happy
166        # Note that testMkimageImagename() relies on this 'mkimage' parameter
167        fake_size = 1024
168        if self._multiple_data_files:
169            fnames = []
170            uniq = self.GetUniqueName()
171            for entry in self._entries.values():
172                # Put the contents in a temporary file
173                ename = f'mkimage-in-{uniq}-{entry.name}'
174                fname = tools.get_output_filename(ename)
175                data = entry.GetData(required)
176                tools.write_file(fname, data)
177                fnames.append(fname)
178            input_fname = ":".join(fnames)
179            data = b''
180        else:
181            data, input_fname, uniq = self.collect_contents_to_file(
182                self._entries.values(), 'mkimage', fake_size)
183        if self._imagename:
184            image_data, imagename_fname, _ = self.collect_contents_to_file(
185                [self._imagename], 'mkimage-n', 1024)
186        outfile = self._filename if self._filename else 'mkimage-out.%s' % uniq
187        output_fname = tools.get_output_filename(outfile)
188
189        missing_list = []
190        self.CheckMissing(missing_list)
191        self.missing = bool(missing_list)
192        if self.missing:
193            return b''
194
195        args = ['-d', input_fname]
196        if self._data_to_imagename:
197            args += ['-n', input_fname]
198        elif self._imagename:
199            args += ['-n', imagename_fname]
200        args += self._args + [output_fname]
201        if self.mkimage.run_cmd(*args) is not None:
202            return tools.read_file(output_fname)
203        else:
204            # Bintool is missing; just use the input data as the output
205            self.record_missing_bintool(self.mkimage)
206            return data
207
208    def GetEntries(self):
209        # Make a copy so we don't change the original
210        entries = OrderedDict(self._entries)
211        if self._imagename:
212            entries['imagename'] = self._imagename
213        return entries
214
215    def AddBintools(self, btools):
216        super().AddBintools(btools)
217        self.mkimage = self.AddBintool(btools, 'mkimage')
218
219    def CheckEntries(self):
220        pass
221
222    def ProcessContents(self):
223        # The blob may have changed due to WriteSymbols()
224        ok = super().ProcessContents()
225        data = self.BuildSectionData(True)
226        ok2 = self.ProcessContentsUpdate(data)
227        return ok and ok2
228
229    def SetImagePos(self, image_pos):
230        """Set the position in the image
231
232        This sets each subentry's offsets, sizes and positions-in-image
233        according to where they ended up in the packed mkimage file.
234
235        NOTE: This assumes a legacy mkimage and assumes that the images are
236        written to the output in order. SoC-specific mkimage handling may not
237        conform to this, in which case these values may be wrong.
238
239        Args:
240            image_pos (int): Position of this entry in the image
241        """
242        # The mkimage header consists of 0x40 bytes, following by a table of
243        # offsets for each file
244        upto = 0x40
245
246        # Skip the 0-terminated list of offsets (assume a single image)
247        upto += 4 + 4
248        for entry in self.GetEntries().values():
249            entry.SetOffsetSize(upto, None)
250
251            # Give up if any entries lack a size
252            if entry.size is None:
253                return
254            upto += entry.size
255
256        super().SetImagePos(image_pos)
257