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