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