binman: Simplify state.fdt_subset
[oweals/u-boot.git] / tools / binman / state.py
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
8 import hashlib
9 import re
10
11 import os
12 import tools
13
14 # Records the device-tree files known to binman, keyed by entry type (e.g.
15 # 'u-boot-spl-dtb'). These are the output FDT files, which can be updated by
16 # binman. They have been copied to <xxx>.out files.
17 #
18 #   key: entry type
19 #   value: tuple:
20 #       Fdt object
21 #       Filename
22 output_fdt_files = {}
23
24 # Arguments passed to binman to provide arguments to entries
25 entry_args = {}
26
27 # True to use fake device-tree files for testing (see U_BOOT_DTB_DATA in
28 # ftest.py)
29 use_fake_dtb = False
30
31 # Dict of device trees, keyed by entry type. These are the input device trees,
32 # before any modification by U-Boot
33 # The value is as returned by Entry.GetFdts(), i.e. a tuple:
34 #     Fdt object for this dtb, or None if not available
35 #     Filename of file containing this dtb
36 fdt_set = {}
37
38 # The DTB which contains the full image information
39 main_dtb = None
40
41 # Allow entries to expand after they have been packed. This is detected and
42 # forces a re-pack. If not allowed, any attempted expansion causes an error in
43 # Entry.ProcessContentsUpdate()
44 allow_entry_expansion = True
45
46 def GetFdtForEtype(etype):
47     """Get the Fdt object for a particular device-tree entry
48
49     Binman keeps track of at least one device-tree file called u-boot.dtb but
50     can also have others (e.g. for SPL). This function looks up the given
51     entry and returns the associated Fdt object.
52
53     Args:
54         etype: Entry type of device tree (e.g. 'u-boot-dtb')
55
56     Returns:
57         Fdt object associated with the entry type
58     """
59     return output_fdt_files[etype][0]
60
61 def GetFdtPath(etype):
62     """Get the full pathname of a particular Fdt object
63
64     Similar to GetFdtForEtype() but returns the pathname associated with the
65     Fdt.
66
67     Args:
68         etype: Entry type of device tree (e.g. 'u-boot-dtb')
69
70     Returns:
71         Full path name to the associated Fdt
72     """
73     return output_fdt_files[etype][0]._fname
74
75 def GetFdtContents(etype='u-boot-dtb'):
76     """Looks up the FDT pathname and contents
77
78     This is used to obtain the Fdt pathname and contents when needed by an
79     entry. It supports a 'fake' dtb, allowing tests to substitute test data for
80     the real dtb.
81
82     Args:
83         etype: Entry type to look up (e.g. 'u-boot.dtb').
84
85     Returns:
86         tuple:
87             pathname to Fdt
88             Fdt data (as bytes)
89     """
90     if etype in output_fdt_files and not use_fake_dtb:
91         pathname = GetFdtPath(etype)
92         data = GetFdtForEtype(etype).GetContents()
93     else:
94         fname = output_fdt_files[etype][1]
95         pathname = tools.GetInputFilename(fname)
96         data = tools.ReadFile(pathname)
97     return pathname, data
98
99 def SetEntryArgs(args):
100     """Set the value of the entry args
101
102     This sets up the entry_args dict which is used to supply entry arguments to
103     entries.
104
105     Args:
106         args: List of entry arguments, each in the format "name=value"
107     """
108     global entry_args
109
110     entry_args = {}
111     if args:
112         for arg in args:
113             m = re.match('([^=]*)=(.*)', arg)
114             if not m:
115                 raise ValueError("Invalid entry arguemnt '%s'" % arg)
116             entry_args[m.group(1)] = m.group(2)
117
118 def GetEntryArg(name):
119     """Get the value of an entry argument
120
121     Args:
122         name: Name of argument to retrieve
123
124     Returns:
125         String value of argument
126     """
127     return entry_args.get(name)
128
129 def Prepare(images, dtb):
130     """Get device tree files ready for use
131
132     This sets up a set of device tree files that can be retrieved by
133     GetAllFdts(). This includes U-Boot proper and any SPL device trees.
134
135     Args:
136         images: List of images being used
137         dtb: Main dtb
138     """
139     global fdt_set, output_fdt_files, main_dtb
140     # Import these here in case libfdt.py is not available, in which case
141     # the above help option still works.
142     import fdt
143     import fdt_util
144
145     # If we are updating the DTBs we need to put these updated versions
146     # where Entry_blob_dtb can find them. We can ignore 'u-boot.dtb'
147     # since it is assumed to be the one passed in with options.dt, and
148     # was handled just above.
149     main_dtb = dtb
150     output_fdt_files.clear()
151     output_fdt_files['u-boot-dtb'] = [dtb, 'u-boot.dtb']
152     output_fdt_files['u-boot-spl-dtb'] = [dtb, 'spl/u-boot-spl.dtb']
153     output_fdt_files['u-boot-tpl-dtb'] = [dtb, 'tpl/u-boot-tpl.dtb']
154     fdt_set = {}
155     if not use_fake_dtb:
156         for image in images.values():
157             fdt_set.update(image.GetFdts())
158         for etype, other in fdt_set.items():
159             _, other_fname = other
160             infile = tools.GetInputFilename(other_fname)
161             other_fname_dtb = fdt_util.EnsureCompiled(infile)
162             out_fname = tools.GetOutputFilename('%s.out' %
163                     os.path.split(other_fname)[1])
164             tools.WriteFile(out_fname, tools.ReadFile(other_fname_dtb))
165             other_dtb = fdt.FdtScan(out_fname)
166             output_fdt_files[etype] = [other_dtb, other_fname]
167
168 def GetAllFdts():
169     """Yield all device tree files being used by binman
170
171     Yields:
172         Device trees being used (U-Boot proper, SPL, TPL)
173     """
174     yield main_dtb
175     for etype in fdt_set:
176         dtb = output_fdt_files[etype][0]
177         if dtb != main_dtb:
178             yield dtb
179
180 def GetUpdateNodes(node):
181     """Yield all the nodes that need to be updated in all device trees
182
183     The property referenced by this node is added to any device trees which
184     have the given node. Due to removable of unwanted notes, SPL and TPL may
185     not have this node.
186
187     Args:
188         node: Node object in the main device tree to look up
189
190     Yields:
191         Node objects in each device tree that is in use (U-Boot proper, which
192             is node, SPL and TPL)
193     """
194     yield node
195     for dtb, fname in output_fdt_files.values():
196         if dtb != node.GetFdt():
197             other_node = dtb.GetNode(node.path)
198             if other_node:
199                 yield other_node
200
201 def AddZeroProp(node, prop):
202     """Add a new property to affected device trees with an integer value of 0.
203
204     Args:
205         prop_name: Name of property
206     """
207     for n in GetUpdateNodes(node):
208         n.AddZeroProp(prop)
209
210 def AddSubnode(node, name):
211     """Add a new subnode to a node in affected device trees
212
213     Args:
214         node: Node to add to
215         name: name of node to add
216
217     Returns:
218         New subnode that was created in main tree
219     """
220     first = None
221     for n in GetUpdateNodes(node):
222         subnode = n.AddSubnode(name)
223         if not first:
224             first = subnode
225     return first
226
227 def AddString(node, prop, value):
228     """Add a new string property to affected device trees
229
230     Args:
231         prop_name: Name of property
232         value: String value (which will be \0-terminated in the DT)
233     """
234     for n in GetUpdateNodes(node):
235         n.AddString(prop, value)
236
237 def SetInt(node, prop, value):
238     """Update an integer property in affected device trees with an integer value
239
240     This is not allowed to change the size of the FDT.
241
242     Args:
243         prop_name: Name of property
244     """
245     for n in GetUpdateNodes(node):
246         n.SetInt(prop, value)
247
248 def CheckAddHashProp(node):
249     hash_node = node.FindNode('hash')
250     if hash_node:
251         algo = hash_node.props.get('algo')
252         if not algo:
253             return "Missing 'algo' property for hash node"
254         if algo.value == 'sha256':
255             size = 32
256         else:
257             return "Unknown hash algorithm '%s'" % algo
258         for n in GetUpdateNodes(hash_node):
259             n.AddEmptyProp('value', size)
260
261 def CheckSetHashValue(node, get_data_func):
262     hash_node = node.FindNode('hash')
263     if hash_node:
264         algo = hash_node.props.get('algo').value
265         if algo == 'sha256':
266             m = hashlib.sha256()
267             m.update(get_data_func())
268             data = m.digest()
269         for n in GetUpdateNodes(hash_node):
270             n.SetData('value', data)
271
272 def SetAllowEntryExpansion(allow):
273     """Set whether post-pack expansion of entries is allowed
274
275     Args:
276        allow: True to allow expansion, False to raise an exception
277     """
278     global allow_entry_expansion
279
280     allow_entry_expansion = allow
281
282 def AllowEntryExpansion():
283     """Check whether post-pack expansion of entries is allowed
284
285     Returns:
286         True if expansion should be allowed, False if an exception should be
287             raised
288     """
289     return allow_entry_expansion