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