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