1# SPDX-License-Identifier: GPL-2.0+
2# Copyright 2018 Google, Inc
3# Written by Simon Glass <sjg@chromium.org>
4#
5# Holds and modifies the state information held by binman
6#
7
8import hashlib
9import re
10
11from dtoc import fdt
12import os
13from patman import tools
14from patman import tout
15
16# Records the device-tree files known to binman, keyed by entry type (e.g.
17# 'u-boot-spl-dtb'). These are the output FDT files, which can be updated by
18# binman. They have been copied to <xxx>.out files.
19#
20#   key: entry type
21#   value: tuple:
22#       Fdt object
23#       Filename
24#       Entry object, or None if not known
25output_fdt_info = {}
26
27# Prefix to add to an fdtmap path to turn it into a path to the /binman node
28fdt_path_prefix = ''
29
30# Arguments passed to binman to provide arguments to entries
31entry_args = {}
32
33# True to use fake device-tree files for testing (see U_BOOT_DTB_DATA in
34# ftest.py)
35use_fake_dtb = False
36
37# The DTB which contains the full image information
38main_dtb = None
39
40# Allow entries to expand after they have been packed. This is detected and
41# forces a re-pack. If not allowed, any attempted expansion causes an error in
42# Entry.ProcessContentsUpdate()
43allow_entry_expansion = True
44
45# Don't allow entries to contract after they have been packed. Instead just
46# leave some wasted space. If allowed, this is detected and forces a re-pack,
47# but may result in entries that oscillate in size, thus causing a pack error.
48# An example is a compressed device tree where the original offset values
49# result in a larger compressed size than the new ones, but then after updating
50# to the new ones, the compressed size increases, etc.
51allow_entry_contraction = False
52
53def GetFdtForEtype(etype):
54    """Get the Fdt object for a particular device-tree entry
55
56    Binman keeps track of at least one device-tree file called u-boot.dtb but
57    can also have others (e.g. for SPL). This function looks up the given
58    entry and returns the associated Fdt object.
59
60    Args:
61        etype: Entry type of device tree (e.g. 'u-boot-dtb')
62
63    Returns:
64        Fdt object associated with the entry type
65    """
66    value = output_fdt_info.get(etype);
67    if not value:
68        return None
69    return value[0]
70
71def GetFdtPath(etype):
72    """Get the full pathname of a particular Fdt object
73
74    Similar to GetFdtForEtype() but returns the pathname associated with the
75    Fdt.
76
77    Args:
78        etype: Entry type of device tree (e.g. 'u-boot-dtb')
79
80    Returns:
81        Full path name to the associated Fdt
82    """
83    return output_fdt_info[etype][0]._fname
84
85def GetFdtContents(etype='u-boot-dtb'):
86    """Looks up the FDT pathname and contents
87
88    This is used to obtain the Fdt pathname and contents when needed by an
89    entry. It supports a 'fake' dtb, allowing tests to substitute test data for
90    the real dtb.
91
92    Args:
93        etype: Entry type to look up (e.g. 'u-boot.dtb').
94
95    Returns:
96        tuple:
97            pathname to Fdt
98            Fdt data (as bytes)
99    """
100    if etype not in output_fdt_info:
101        return None, None
102    if not use_fake_dtb:
103        pathname = GetFdtPath(etype)
104        data = GetFdtForEtype(etype).GetContents()
105    else:
106        fname = output_fdt_info[etype][1]
107        pathname = tools.GetInputFilename(fname)
108        data = tools.ReadFile(pathname)
109    return pathname, data
110
111def UpdateFdtContents(etype, data):
112    """Update the contents of a particular device tree
113
114    The device tree is updated and written back to its file. This affects what
115    is returned from future called to GetFdtContents(), etc.
116
117    Args:
118        etype: Entry type (e.g. 'u-boot-dtb')
119        data: Data to replace the DTB with
120    """
121    dtb, fname, entry = output_fdt_info[etype]
122    dtb_fname = dtb.GetFilename()
123    tools.WriteFile(dtb_fname, data)
124    dtb = fdt.FdtScan(dtb_fname)
125    output_fdt_info[etype] = [dtb, fname, entry]
126
127def SetEntryArgs(args):
128    """Set the value of the entry args
129
130    This sets up the entry_args dict which is used to supply entry arguments to
131    entries.
132
133    Args:
134        args: List of entry arguments, each in the format "name=value"
135    """
136    global entry_args
137
138    entry_args = {}
139    if args:
140        for arg in args:
141            m = re.match('([^=]*)=(.*)', arg)
142            if not m:
143                raise ValueError("Invalid entry arguemnt '%s'" % arg)
144            entry_args[m.group(1)] = m.group(2)
145
146def GetEntryArg(name):
147    """Get the value of an entry argument
148
149    Args:
150        name: Name of argument to retrieve
151
152    Returns:
153        String value of argument
154    """
155    return entry_args.get(name)
156
157def Prepare(images, dtb):
158    """Get device tree files ready for use
159
160    This sets up a set of device tree files that can be retrieved by
161    GetAllFdts(). This includes U-Boot proper and any SPL device trees.
162
163    Args:
164        images: List of images being used
165        dtb: Main dtb
166    """
167    global output_fdt_info, main_dtb, fdt_path_prefix
168    # Import these here in case libfdt.py is not available, in which case
169    # the above help option still works.
170    from dtoc import fdt
171    from dtoc import fdt_util
172
173    # If we are updating the DTBs we need to put these updated versions
174    # where Entry_blob_dtb can find them. We can ignore 'u-boot.dtb'
175    # since it is assumed to be the one passed in with options.dt, and
176    # was handled just above.
177    main_dtb = dtb
178    output_fdt_info.clear()
179    fdt_path_prefix = ''
180    output_fdt_info['u-boot-dtb'] = [dtb, 'u-boot.dtb', None]
181    output_fdt_info['u-boot-spl-dtb'] = [dtb, 'spl/u-boot-spl.dtb', None]
182    output_fdt_info['u-boot-tpl-dtb'] = [dtb, 'tpl/u-boot-tpl.dtb', None]
183    if not use_fake_dtb:
184        fdt_set = {}
185        for image in images.values():
186            fdt_set.update(image.GetFdts())
187        for etype, other in fdt_set.items():
188            entry, other_fname = other
189            infile = tools.GetInputFilename(other_fname)
190            other_fname_dtb = fdt_util.EnsureCompiled(infile)
191            out_fname = tools.GetOutputFilename('%s.out' %
192                    os.path.split(other_fname)[1])
193            tools.WriteFile(out_fname, tools.ReadFile(other_fname_dtb))
194            other_dtb = fdt.FdtScan(out_fname)
195            output_fdt_info[etype] = [other_dtb, out_fname, entry]
196
197def PrepareFromLoadedData(image):
198    """Get device tree files ready for use with a loaded image
199
200    Loaded images are different from images that are being created by binman,
201    since there is generally already an fdtmap and we read the description from
202    that. This provides the position and size of every entry in the image with
203    no calculation required.
204
205    This function uses the same output_fdt_info[] as Prepare(). It finds the
206    device tree files, adds a reference to the fdtmap and sets the FDT path
207    prefix to translate from the fdtmap (where the root node is the image node)
208    to the normal device tree (where the image node is under a /binman node).
209
210    Args:
211        images: List of images being used
212    """
213    global output_fdt_info, main_dtb, fdt_path_prefix
214
215    tout.Info('Preparing device trees')
216    output_fdt_info.clear()
217    fdt_path_prefix = ''
218    output_fdt_info['fdtmap'] = [image.fdtmap_dtb, 'u-boot.dtb', None]
219    main_dtb = None
220    tout.Info("   Found device tree type 'fdtmap' '%s'" % image.fdtmap_dtb.name)
221    for etype, value in image.GetFdts().items():
222        entry, fname = value
223        out_fname = tools.GetOutputFilename('%s.dtb' % entry.etype)
224        tout.Info("   Found device tree type '%s' at '%s' path '%s'" %
225                  (etype, out_fname, entry.GetPath()))
226        entry._filename = entry.GetDefaultFilename()
227        data = entry.ReadData()
228
229        tools.WriteFile(out_fname, data)
230        dtb = fdt.Fdt(out_fname)
231        dtb.Scan()
232        image_node = dtb.GetNode('/binman')
233        if 'multiple-images' in image_node.props:
234            image_node = dtb.GetNode('/binman/%s' % image.image_node)
235        fdt_path_prefix = image_node.path
236        output_fdt_info[etype] = [dtb, None, entry]
237    tout.Info("   FDT path prefix '%s'" % fdt_path_prefix)
238
239
240def GetAllFdts():
241    """Yield all device tree files being used by binman
242
243    Yields:
244        Device trees being used (U-Boot proper, SPL, TPL)
245    """
246    if main_dtb:
247        yield main_dtb
248    for etype in output_fdt_info:
249        dtb = output_fdt_info[etype][0]
250        if dtb != main_dtb:
251            yield dtb
252
253def GetUpdateNodes(node, for_repack=False):
254    """Yield all the nodes that need to be updated in all device trees
255
256    The property referenced by this node is added to any device trees which
257    have the given node. Due to removable of unwanted notes, SPL and TPL may
258    not have this node.
259
260    Args:
261        node: Node object in the main device tree to look up
262        for_repack: True if we want only nodes which need 'repack' properties
263            added to them (e.g. 'orig-offset'), False to return all nodes. We
264            don't add repack properties to SPL/TPL device trees.
265
266    Yields:
267        Node objects in each device tree that is in use (U-Boot proper, which
268            is node, SPL and TPL)
269    """
270    yield node
271    for dtb, fname, entry in output_fdt_info.values():
272        if dtb != node.GetFdt():
273            if for_repack and entry.etype != 'u-boot-dtb':
274                continue
275            other_node = dtb.GetNode(fdt_path_prefix + node.path)
276            #print('   try', fdt_path_prefix + node.path, other_node)
277            if other_node:
278                yield other_node
279
280def AddZeroProp(node, prop, for_repack=False):
281    """Add a new property to affected device trees with an integer value of 0.
282
283    Args:
284        prop_name: Name of property
285        for_repack: True is this property is only needed for repacking
286    """
287    for n in GetUpdateNodes(node, for_repack):
288        n.AddZeroProp(prop)
289
290def AddSubnode(node, name):
291    """Add a new subnode to a node in affected device trees
292
293    Args:
294        node: Node to add to
295        name: name of node to add
296
297    Returns:
298        New subnode that was created in main tree
299    """
300    first = None
301    for n in GetUpdateNodes(node):
302        subnode = n.AddSubnode(name)
303        if not first:
304            first = subnode
305    return first
306
307def AddString(node, prop, value):
308    """Add a new string property to affected device trees
309
310    Args:
311        prop_name: Name of property
312        value: String value (which will be \0-terminated in the DT)
313    """
314    for n in GetUpdateNodes(node):
315        n.AddString(prop, value)
316
317def AddInt(node, prop, value):
318    """Add a new string property to affected device trees
319
320    Args:
321        prop_name: Name of property
322        val: Integer value of property
323    """
324    for n in GetUpdateNodes(node):
325        n.AddInt(prop, value)
326
327def SetInt(node, prop, value, for_repack=False):
328    """Update an integer property in affected device trees with an integer value
329
330    This is not allowed to change the size of the FDT.
331
332    Args:
333        prop_name: Name of property
334        for_repack: True is this property is only needed for repacking
335    """
336    for n in GetUpdateNodes(node, for_repack):
337        tout.Detail("File %s: Update node '%s' prop '%s' to %#x" %
338                    (n.GetFdt().name, n.path, prop, value))
339        n.SetInt(prop, value)
340
341def CheckAddHashProp(node):
342    hash_node = node.FindNode('hash')
343    if hash_node:
344        algo = hash_node.props.get('algo')
345        if not algo:
346            return "Missing 'algo' property for hash node"
347        if algo.value == 'sha256':
348            size = 32
349        else:
350            return "Unknown hash algorithm '%s'" % algo
351        for n in GetUpdateNodes(hash_node):
352            n.AddEmptyProp('value', size)
353
354def CheckSetHashValue(node, get_data_func):
355    hash_node = node.FindNode('hash')
356    if hash_node:
357        algo = hash_node.props.get('algo').value
358        if algo == 'sha256':
359            m = hashlib.sha256()
360            m.update(get_data_func())
361            data = m.digest()
362        for n in GetUpdateNodes(hash_node):
363            n.SetData('value', data)
364
365def SetAllowEntryExpansion(allow):
366    """Set whether post-pack expansion of entries is allowed
367
368    Args:
369       allow: True to allow expansion, False to raise an exception
370    """
371    global allow_entry_expansion
372
373    allow_entry_expansion = allow
374
375def AllowEntryExpansion():
376    """Check whether post-pack expansion of entries is allowed
377
378    Returns:
379        True if expansion should be allowed, False if an exception should be
380            raised
381    """
382    return allow_entry_expansion
383
384def SetAllowEntryContraction(allow):
385    """Set whether post-pack contraction of entries is allowed
386
387    Args:
388       allow: True to allow contraction, False to raise an exception
389    """
390    global allow_entry_contraction
391
392    allow_entry_contraction = allow
393
394def AllowEntryContraction():
395    """Check whether post-pack contraction of entries is allowed
396
397    Returns:
398        True if contraction should be allowed, False if an exception should be
399            raised
400    """
401    return allow_entry_contraction
402