state.py 12.8 KB
Newer Older
1
2
3
4
5
6
7
# SPDX-License-Identifier: GPL-2.0+
# Copyright 2018 Google, Inc
# Written by Simon Glass <sjg@chromium.org>
#
# Holds and modifies the state information held by binman
#

Simon Glass's avatar
Simon Glass committed
8
import hashlib
9
10
import re

Simon Glass's avatar
Simon Glass committed
11
from dtoc import fdt
12
import os
Simon Glass's avatar
Simon Glass committed
13
14
from patman import tools
from patman import tout
15

16
17
18
19
20
21
# Map an dtb etype to its expected filename
DTB_TYPE_FNAME = {
    'u-boot-spl-dtb': 'spl/u-boot-spl.dtb',
    'u-boot-tpl-dtb': 'tpl/u-boot-tpl.dtb',
    }

22
23
24
25
26
27
28
29
# Records the device-tree files known to binman, keyed by entry type (e.g.
# 'u-boot-spl-dtb'). These are the output FDT files, which can be updated by
# binman. They have been copied to <xxx>.out files.
#
#   key: entry type
#   value: tuple:
#       Fdt object
#       Filename
30
31
#       Entry object, or None if not known
output_fdt_info = {}
32

33
34
35
# Prefix to add to an fdtmap path to turn it into a path to the /binman node
fdt_path_prefix = ''

36
37
38
# Arguments passed to binman to provide arguments to entries
entry_args = {}

39
40
# True to use fake device-tree files for testing (see U_BOOT_DTB_DATA in
# ftest.py)
41
use_fake_dtb = False
42

43
44
45
# The DTB which contains the full image information
main_dtb = None

46
47
48
49
50
# Allow entries to expand after they have been packed. This is detected and
# forces a re-pack. If not allowed, any attempted expansion causes an error in
# Entry.ProcessContentsUpdate()
allow_entry_expansion = True

51
52
53
54
55
56
57
58
# Don't allow entries to contract after they have been packed. Instead just
# leave some wasted space. If allowed, this is detected and forces a re-pack,
# but may result in entries that oscillate in size, thus causing a pack error.
# An example is a compressed device tree where the original offset values
# result in a larger compressed size than the new ones, but then after updating
# to the new ones, the compressed size increases, etc.
allow_entry_contraction = False

59
60
def GetFdtForEtype(etype):
    """Get the Fdt object for a particular device-tree entry
61
62
63

    Binman keeps track of at least one device-tree file called u-boot.dtb but
    can also have others (e.g. for SPL). This function looks up the given
64
    entry and returns the associated Fdt object.
65
66

    Args:
67
        etype: Entry type of device tree (e.g. 'u-boot-dtb')
68
69

    Returns:
70
        Fdt object associated with the entry type
71
    """
72
    value = output_fdt_info.get(etype);
73
74
75
    if not value:
        return None
    return value[0]
76

77
def GetFdtPath(etype):
78
79
    """Get the full pathname of a particular Fdt object

Simon Glass's avatar
Simon Glass committed
80
81
    Similar to GetFdtForEtype() but returns the pathname associated with the
    Fdt.
82
83

    Args:
84
        etype: Entry type of device tree (e.g. 'u-boot-dtb')
85
86
87
88

    Returns:
        Full path name to the associated Fdt
    """
89
    return output_fdt_info[etype][0]._fname
90

91
def GetFdtContents(etype='u-boot-dtb'):
92
93
94
95
96
97
98
    """Looks up the FDT pathname and contents

    This is used to obtain the Fdt pathname and contents when needed by an
    entry. It supports a 'fake' dtb, allowing tests to substitute test data for
    the real dtb.

    Args:
99
        etype: Entry type to look up (e.g. 'u-boot.dtb').
100
101
102
103
104
105

    Returns:
        tuple:
            pathname to Fdt
            Fdt data (as bytes)
    """
106
    if etype not in output_fdt_info:
107
108
        return None, None
    if not use_fake_dtb:
109
110
        pathname = GetFdtPath(etype)
        data = GetFdtForEtype(etype).GetContents()
111
    else:
112
        fname = output_fdt_info[etype][1]
113
114
115
116
        pathname = tools.GetInputFilename(fname)
        data = tools.ReadFile(pathname)
    return pathname, data

117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
def UpdateFdtContents(etype, data):
    """Update the contents of a particular device tree

    The device tree is updated and written back to its file. This affects what
    is returned from future called to GetFdtContents(), etc.

    Args:
        etype: Entry type (e.g. 'u-boot-dtb')
        data: Data to replace the DTB with
    """
    dtb, fname, entry = output_fdt_info[etype]
    dtb_fname = dtb.GetFilename()
    tools.WriteFile(dtb_fname, data)
    dtb = fdt.FdtScan(dtb_fname)
    output_fdt_info[etype] = [dtb, fname, entry]

133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
def SetEntryArgs(args):
    """Set the value of the entry args

    This sets up the entry_args dict which is used to supply entry arguments to
    entries.

    Args:
        args: List of entry arguments, each in the format "name=value"
    """
    global entry_args

    entry_args = {}
    if args:
        for arg in args:
            m = re.match('([^=]*)=(.*)', arg)
            if not m:
                raise ValueError("Invalid entry arguemnt '%s'" % arg)
            entry_args[m.group(1)] = m.group(2)

def GetEntryArg(name):
    """Get the value of an entry argument

    Args:
        name: Name of argument to retrieve

    Returns:
        String value of argument
    """
    return entry_args.get(name)
162

163
def Prepare(images, dtb):
164
165
    """Get device tree files ready for use

Simon Glass's avatar
Simon Glass committed
166
167
    This sets up a set of device tree files that can be retrieved by
    GetAllFdts(). This includes U-Boot proper and any SPL device trees.
168
169

    Args:
170
        images: List of images being used
171
172
        dtb: Main dtb
    """
173
    global output_fdt_info, main_dtb, fdt_path_prefix
174
175
    # Import these here in case libfdt.py is not available, in which case
    # the above help option still works.
Simon Glass's avatar
Simon Glass committed
176
177
    from dtoc import fdt
    from dtoc import fdt_util
178
179
180
181
182
183

    # If we are updating the DTBs we need to put these updated versions
    # where Entry_blob_dtb can find them. We can ignore 'u-boot.dtb'
    # since it is assumed to be the one passed in with options.dt, and
    # was handled just above.
    main_dtb = dtb
184
    output_fdt_info.clear()
185
    fdt_path_prefix = ''
186
    output_fdt_info['u-boot-dtb'] = [dtb, 'u-boot.dtb', None]
187
188
189
190
    if use_fake_dtb:
        for etype, fname in DTB_TYPE_FNAME.items():
            output_fdt_info[etype] = [dtb, fname, None]
    else:
191
        fdt_set = {}
192
        for image in images.values():
193
194
            fdt_set.update(image.GetFdts())
        for etype, other in fdt_set.items():
195
196
197
            entry, fname = other
            infile = tools.GetInputFilename(fname)
            fname_dtb = fdt_util.EnsureCompiled(infile)
198
            out_fname = tools.GetOutputFilename('%s.out' %
199
200
                    os.path.split(fname)[1])
            tools.WriteFile(out_fname, tools.ReadFile(fname_dtb))
201
            other_dtb = fdt.FdtScan(out_fname)
202
            output_fdt_info[etype] = [other_dtb, out_fname, entry]
203

204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
def PrepareFromLoadedData(image):
    """Get device tree files ready for use with a loaded image

    Loaded images are different from images that are being created by binman,
    since there is generally already an fdtmap and we read the description from
    that. This provides the position and size of every entry in the image with
    no calculation required.

    This function uses the same output_fdt_info[] as Prepare(). It finds the
    device tree files, adds a reference to the fdtmap and sets the FDT path
    prefix to translate from the fdtmap (where the root node is the image node)
    to the normal device tree (where the image node is under a /binman node).

    Args:
        images: List of images being used
    """
    global output_fdt_info, main_dtb, fdt_path_prefix

    tout.Info('Preparing device trees')
    output_fdt_info.clear()
    fdt_path_prefix = ''
    output_fdt_info['fdtmap'] = [image.fdtmap_dtb, 'u-boot.dtb', None]
    main_dtb = None
    tout.Info("   Found device tree type 'fdtmap' '%s'" % image.fdtmap_dtb.name)
    for etype, value in image.GetFdts().items():
        entry, fname = value
        out_fname = tools.GetOutputFilename('%s.dtb' % entry.etype)
        tout.Info("   Found device tree type '%s' at '%s' path '%s'" %
                  (etype, out_fname, entry.GetPath()))
        entry._filename = entry.GetDefaultFilename()
        data = entry.ReadData()

        tools.WriteFile(out_fname, data)
        dtb = fdt.Fdt(out_fname)
        dtb.Scan()
        image_node = dtb.GetNode('/binman')
        if 'multiple-images' in image_node.props:
            image_node = dtb.GetNode('/binman/%s' % image.image_node)
        fdt_path_prefix = image_node.path
        output_fdt_info[etype] = [dtb, None, entry]
    tout.Info("   FDT path prefix '%s'" % fdt_path_prefix)


Simon Glass's avatar
Simon Glass committed
247
def GetAllFdts():
248
249
250
251
252
    """Yield all device tree files being used by binman

    Yields:
        Device trees being used (U-Boot proper, SPL, TPL)
    """
253
254
    if main_dtb:
        yield main_dtb
255
256
    for etype in output_fdt_info:
        dtb = output_fdt_info[etype][0]
257
258
        if dtb != main_dtb:
            yield dtb
259

260
def GetUpdateNodes(node, for_repack=False):
261
262
263
264
265
266
267
268
    """Yield all the nodes that need to be updated in all device trees

    The property referenced by this node is added to any device trees which
    have the given node. Due to removable of unwanted notes, SPL and TPL may
    not have this node.

    Args:
        node: Node object in the main device tree to look up
269
270
271
        for_repack: True if we want only nodes which need 'repack' properties
            added to them (e.g. 'orig-offset'), False to return all nodes. We
            don't add repack properties to SPL/TPL device trees.
272
273
274
275
276
277

    Yields:
        Node objects in each device tree that is in use (U-Boot proper, which
            is node, SPL and TPL)
    """
    yield node
278
    for dtb, fname, entry in output_fdt_info.values():
279
        if dtb != node.GetFdt():
280
281
            if for_repack and entry.etype != 'u-boot-dtb':
                continue
282
            other_node = dtb.GetNode(fdt_path_prefix + node.path)
283
            #print('   try', fdt_path_prefix + node.path, other_node)
284
285
            if other_node:
                yield other_node
286

287
def AddZeroProp(node, prop, for_repack=False):
288
289
290
291
    """Add a new property to affected device trees with an integer value of 0.

    Args:
        prop_name: Name of property
292
        for_repack: True is this property is only needed for repacking
293
    """
294
    for n in GetUpdateNodes(node, for_repack):
295
296
        n.AddZeroProp(prop)

Simon Glass's avatar
Simon Glass committed
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
def AddSubnode(node, name):
    """Add a new subnode to a node in affected device trees

    Args:
        node: Node to add to
        name: name of node to add

    Returns:
        New subnode that was created in main tree
    """
    first = None
    for n in GetUpdateNodes(node):
        subnode = n.AddSubnode(name)
        if not first:
            first = subnode
    return first

def AddString(node, prop, value):
    """Add a new string property to affected device trees

    Args:
        prop_name: Name of property
        value: String value (which will be \0-terminated in the DT)
    """
    for n in GetUpdateNodes(node):
        n.AddString(prop, value)

324
325
326
327
328
329
330
331
332
333
def AddInt(node, prop, value):
    """Add a new string property to affected device trees

    Args:
        prop_name: Name of property
        val: Integer value of property
    """
    for n in GetUpdateNodes(node):
        n.AddInt(prop, value)

334
def SetInt(node, prop, value, for_repack=False):
335
336
337
338
339
340
    """Update an integer property in affected device trees with an integer value

    This is not allowed to change the size of the FDT.

    Args:
        prop_name: Name of property
341
        for_repack: True is this property is only needed for repacking
342
    """
343
344
    for n in GetUpdateNodes(node, for_repack):
        tout.Detail("File %s: Update node '%s' prop '%s' to %#x" %
345
                    (n.GetFdt().name, n.path, prop, value))
346
        n.SetInt(prop, value)
Simon Glass's avatar
Simon Glass committed
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370

def CheckAddHashProp(node):
    hash_node = node.FindNode('hash')
    if hash_node:
        algo = hash_node.props.get('algo')
        if not algo:
            return "Missing 'algo' property for hash node"
        if algo.value == 'sha256':
            size = 32
        else:
            return "Unknown hash algorithm '%s'" % algo
        for n in GetUpdateNodes(hash_node):
            n.AddEmptyProp('value', size)

def CheckSetHashValue(node, get_data_func):
    hash_node = node.FindNode('hash')
    if hash_node:
        algo = hash_node.props.get('algo').value
        if algo == 'sha256':
            m = hashlib.sha256()
            m.update(get_data_func())
            data = m.digest()
        for n in GetUpdateNodes(hash_node):
            n.SetData('value', data)
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389

def SetAllowEntryExpansion(allow):
    """Set whether post-pack expansion of entries is allowed

    Args:
       allow: True to allow expansion, False to raise an exception
    """
    global allow_entry_expansion

    allow_entry_expansion = allow

def AllowEntryExpansion():
    """Check whether post-pack expansion of entries is allowed

    Returns:
        True if expansion should be allowed, False if an exception should be
            raised
    """
    return allow_entry_expansion
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408

def SetAllowEntryContraction(allow):
    """Set whether post-pack contraction of entries is allowed

    Args:
       allow: True to allow contraction, False to raise an exception
    """
    global allow_entry_contraction

    allow_entry_contraction = allow

def AllowEntryContraction():
    """Check whether post-pack contraction of entries is allowed

    Returns:
        True if contraction should be allowed, False if an exception should be
            raised
    """
    return allow_entry_contraction