blob: 03dfa2f805c4708a7081c8053722ff59c231abd8 [file] [log] [blame]
Simon Glasseca32212018-06-01 09:38:12 -06001# SPDX-License-Identifier: GPL-2.0+
2# Copyright (c) 2018 Google, Inc
3# Written by Simon Glass <sjg@chromium.org>
4#
5# Base class for sections (collections of entries)
6#
7
8from __future__ import print_function
9
10from collections import OrderedDict
11import sys
12
13import fdt_util
14import re
Simon Glassc8135dc2018-09-14 04:57:21 -060015import state
Simon Glasseca32212018-06-01 09:38:12 -060016import tools
17
18class Section(object):
19 """A section which contains multiple entries
20
21 A section represents a collection of entries. There must be one or more
22 sections in an image. Sections are used to group entries together.
23
24 Attributes:
25 _node: Node object that contains the section definition in device tree
Simon Glass7c767ad2018-09-14 04:57:33 -060026 _parent_section: Parent Section object which created this Section
Simon Glasseca32212018-06-01 09:38:12 -060027 _size: Section size in bytes, or None if not known yet
28 _align_size: Section size alignment, or None
29 _pad_before: Number of bytes before the first entry starts. This
Simon Glasse8561af2018-08-01 15:22:37 -060030 effectively changes the place where entry offset 0 starts
Simon Glasseca32212018-06-01 09:38:12 -060031 _pad_after: Number of bytes after the last entry ends. The last
32 entry will finish on or before this boundary
33 _pad_byte: Byte to use to pad the section where there is no entry
Simon Glasse8561af2018-08-01 15:22:37 -060034 _sort: True if entries should be sorted by offset, False if they
Simon Glasseca32212018-06-01 09:38:12 -060035 must be in-order in the device tree description
36 _skip_at_start: Number of bytes before the first entry starts. These
Simon Glasse8561af2018-08-01 15:22:37 -060037 effectively adjust the starting offset of entries. For example,
Simon Glasseca32212018-06-01 09:38:12 -060038 if _pad_before is 16, then the first entry would start at 16.
Simon Glasse8561af2018-08-01 15:22:37 -060039 An entry with offset = 20 would in fact be written at offset 4
Simon Glasseca32212018-06-01 09:38:12 -060040 in the image file.
41 _end_4gb: Indicates that the section ends at the 4GB boundary. This is
Simon Glasse8561af2018-08-01 15:22:37 -060042 used for x86 images, which want to use offsets such that a memory
43 address (like 0xff800000) is the first entry offset. This causes
44 _skip_at_start to be set to the starting memory address.
Simon Glass3b78d532018-06-01 09:38:21 -060045 _name_prefix: Prefix to add to the name of all entries within this
46 section
Simon Glasseca32212018-06-01 09:38:12 -060047 _entries: OrderedDict() of entries
48 """
Simon Glass7c767ad2018-09-14 04:57:33 -060049 def __init__(self, name, parent_section, node, image, test=False):
Simon Glasseca32212018-06-01 09:38:12 -060050 global entry
51 global Entry
52 import entry
53 from entry import Entry
54
Simon Glass7c767ad2018-09-14 04:57:33 -060055 self._parent_section = parent_section
Simon Glass3a9a2b82018-07-17 13:25:28 -060056 self._name = name
Simon Glasseca32212018-06-01 09:38:12 -060057 self._node = node
Simon Glass7c767ad2018-09-14 04:57:33 -060058 self._image = image
Simon Glasseb023b32019-04-25 21:58:39 -060059 self._offset = None
Simon Glasseca32212018-06-01 09:38:12 -060060 self._size = None
61 self._align_size = None
62 self._pad_before = 0
63 self._pad_after = 0
64 self._pad_byte = 0
65 self._sort = False
Jagdish Gediya0fb978c2018-09-03 21:35:07 +053066 self._skip_at_start = None
Simon Glasseca32212018-06-01 09:38:12 -060067 self._end_4gb = False
Simon Glass3b78d532018-06-01 09:38:21 -060068 self._name_prefix = ''
Simon Glasseca32212018-06-01 09:38:12 -060069 self._entries = OrderedDict()
Simon Glassc64aea52018-09-14 04:57:34 -060070 self._image_pos = None
Simon Glasseca32212018-06-01 09:38:12 -060071 if not test:
72 self._ReadNode()
73 self._ReadEntries()
74
75 def _ReadNode(self):
76 """Read properties from the section node"""
Simon Glasseb023b32019-04-25 21:58:39 -060077 self._offset = fdt_util.GetInt(self._node, 'offset')
Simon Glasseca32212018-06-01 09:38:12 -060078 self._size = fdt_util.GetInt(self._node, 'size')
79 self._align_size = fdt_util.GetInt(self._node, 'align-size')
80 if tools.NotPowerOfTwo(self._align_size):
81 self._Raise("Alignment size %s must be a power of two" %
82 self._align_size)
83 self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
84 self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
85 self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
Simon Glasse8561af2018-08-01 15:22:37 -060086 self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
Simon Glasseca32212018-06-01 09:38:12 -060087 self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
Jagdish Gediya0fb978c2018-09-03 21:35:07 +053088 self._skip_at_start = fdt_util.GetInt(self._node, 'skip-at-start')
Simon Glasseca32212018-06-01 09:38:12 -060089 if self._end_4gb:
Jagdish Gediya0fb978c2018-09-03 21:35:07 +053090 if not self._size:
91 self._Raise("Section size must be provided when using end-at-4gb")
92 if self._skip_at_start is not None:
93 self._Raise("Provide either 'end-at-4gb' or 'skip-at-start'")
94 else:
95 self._skip_at_start = 0x100000000 - self._size
96 else:
97 if self._skip_at_start is None:
98 self._skip_at_start = 0
Simon Glass3b78d532018-06-01 09:38:21 -060099 self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
Simon Glasseca32212018-06-01 09:38:12 -0600100
101 def _ReadEntries(self):
102 for node in self._node.subnodes:
Simon Glassae7cf032018-09-14 04:57:31 -0600103 if node.name == 'hash':
104 continue
Simon Glass3b78d532018-06-01 09:38:21 -0600105 entry = Entry.Create(self, node)
106 entry.SetPrefix(self._name_prefix)
107 self._entries[node.name] = entry
Simon Glasseca32212018-06-01 09:38:12 -0600108
Simon Glass0c9d5b52018-09-14 04:57:22 -0600109 def GetFdtSet(self):
110 """Get the set of device tree files used by this image"""
Simon Glass4aea9542019-05-14 15:53:39 -0600111 fdt_set = set()
Simon Glass0c9d5b52018-09-14 04:57:22 -0600112 for entry in self._entries.values():
113 fdt_set.update(entry.GetFdtSet())
114 return fdt_set
115
Simon Glass3a9a2b82018-07-17 13:25:28 -0600116 def SetOffset(self, offset):
117 self._offset = offset
118
Simon Glassac6328c2018-09-14 04:57:28 -0600119 def ExpandEntries(self):
120 for entry in self._entries.values():
121 entry.ExpandEntries()
122
Simon Glasse22f8fa2018-07-06 10:27:41 -0600123 def AddMissingProperties(self):
Simon Glass3a9a2b82018-07-17 13:25:28 -0600124 """Add new properties to the device tree as needed for this entry"""
Simon Glass9dcc8612018-08-01 15:22:42 -0600125 for prop in ['offset', 'size', 'image-pos']:
Simon Glass3a9a2b82018-07-17 13:25:28 -0600126 if not prop in self._node.props:
Simon Glassc8135dc2018-09-14 04:57:21 -0600127 state.AddZeroProp(self._node, prop)
Simon Glassae7cf032018-09-14 04:57:31 -0600128 state.CheckAddHashProp(self._node)
Simon Glasse22f8fa2018-07-06 10:27:41 -0600129 for entry in self._entries.values():
130 entry.AddMissingProperties()
131
132 def SetCalculatedProperties(self):
Simon Glasseb023b32019-04-25 21:58:39 -0600133 state.SetInt(self._node, 'offset', self._offset or 0)
Simon Glassc8135dc2018-09-14 04:57:21 -0600134 state.SetInt(self._node, 'size', self._size)
Simon Glassc64aea52018-09-14 04:57:34 -0600135 image_pos = self._image_pos
136 if self._parent_section:
137 image_pos -= self._parent_section.GetRootSkipAtStart()
138 state.SetInt(self._node, 'image-pos', image_pos)
Simon Glasse22f8fa2018-07-06 10:27:41 -0600139 for entry in self._entries.values():
140 entry.SetCalculatedProperties()
141
Simon Glass92307732018-07-06 10:27:40 -0600142 def ProcessFdt(self, fdt):
143 todo = self._entries.values()
144 for passnum in range(3):
145 next_todo = []
146 for entry in todo:
147 if not entry.ProcessFdt(fdt):
148 next_todo.append(entry)
149 todo = next_todo
150 if not todo:
151 break
152 if todo:
153 self._Raise('Internal error: Could not complete processing of Fdt: '
154 'remaining %s' % todo)
155 return True
156
Simon Glasseca32212018-06-01 09:38:12 -0600157 def CheckSize(self):
158 """Check that the section contents does not exceed its size, etc."""
159 contents_size = 0
160 for entry in self._entries.values():
Simon Glasse8561af2018-08-01 15:22:37 -0600161 contents_size = max(contents_size, entry.offset + entry.size)
Simon Glasseca32212018-06-01 09:38:12 -0600162
163 contents_size -= self._skip_at_start
164
165 size = self._size
166 if not size:
167 size = self._pad_before + contents_size + self._pad_after
168 size = tools.Align(size, self._align_size)
169
170 if self._size and contents_size > self._size:
171 self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
172 (contents_size, contents_size, self._size, self._size))
173 if not self._size:
174 self._size = size
175 if self._size != tools.Align(self._size, self._align_size):
176 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
177 (self._size, self._size, self._align_size, self._align_size))
178 return size
179
180 def _Raise(self, msg):
181 """Raises an error for this section
182
183 Args:
184 msg: Error message to use in the raise string
185 Raises:
186 ValueError()
187 """
188 raise ValueError("Section '%s': %s" % (self._node.path, msg))
189
190 def GetPath(self):
191 """Get the path of an image (in the FDT)
192
193 Returns:
194 Full path of the node for this image
195 """
196 return self._node.path
197
198 def FindEntryType(self, etype):
199 """Find an entry type in the section
200
201 Args:
202 etype: Entry type to find
203 Returns:
204 entry matching that type, or None if not found
205 """
206 for entry in self._entries.values():
207 if entry.etype == etype:
208 return entry
209 return None
210
211 def GetEntryContents(self):
212 """Call ObtainContents() for each entry
213
214 This calls each entry's ObtainContents() a few times until they all
215 return True. We stop calling an entry's function once it returns
216 True. This allows the contents of one entry to depend on another.
217
218 After 3 rounds we give up since it's likely an error.
219 """
220 todo = self._entries.values()
221 for passnum in range(3):
222 next_todo = []
223 for entry in todo:
224 if not entry.ObtainContents():
225 next_todo.append(entry)
226 todo = next_todo
227 if not todo:
228 break
Simon Glass6ba679c2018-07-06 10:27:17 -0600229 if todo:
230 self._Raise('Internal error: Could not complete processing of '
231 'contents: remaining %s' % todo)
232 return True
Simon Glasseca32212018-06-01 09:38:12 -0600233
Simon Glasse8561af2018-08-01 15:22:37 -0600234 def _SetEntryOffsetSize(self, name, offset, size):
235 """Set the offset and size of an entry
Simon Glasseca32212018-06-01 09:38:12 -0600236
237 Args:
238 name: Entry name to update
Simon Glasse8561af2018-08-01 15:22:37 -0600239 offset: New offset
Simon Glasseca32212018-06-01 09:38:12 -0600240 size: New size
241 """
242 entry = self._entries.get(name)
243 if not entry:
Simon Glasse8561af2018-08-01 15:22:37 -0600244 self._Raise("Unable to set offset/size for unknown entry '%s'" %
245 name)
246 entry.SetOffsetSize(self._skip_at_start + offset, size)
Simon Glasseca32212018-06-01 09:38:12 -0600247
Simon Glasse8561af2018-08-01 15:22:37 -0600248 def GetEntryOffsets(self):
249 """Handle entries that want to set the offset/size of other entries
Simon Glasseca32212018-06-01 09:38:12 -0600250
Simon Glasse8561af2018-08-01 15:22:37 -0600251 This calls each entry's GetOffsets() method. If it returns a list
Simon Glasseca32212018-06-01 09:38:12 -0600252 of entries to update, it updates them.
253 """
254 for entry in self._entries.values():
Simon Glasse8561af2018-08-01 15:22:37 -0600255 offset_dict = entry.GetOffsets()
Simon Glass5f3645b2019-05-14 15:53:41 -0600256 for name, info in offset_dict.items():
Simon Glasse8561af2018-08-01 15:22:37 -0600257 self._SetEntryOffsetSize(name, *info)
Simon Glasseca32212018-06-01 09:38:12 -0600258
259 def PackEntries(self):
260 """Pack all entries into the section"""
Simon Glasse8561af2018-08-01 15:22:37 -0600261 offset = self._skip_at_start
Simon Glasseca32212018-06-01 09:38:12 -0600262 for entry in self._entries.values():
Simon Glasse8561af2018-08-01 15:22:37 -0600263 offset = entry.Pack(offset)
264 self._size = self.CheckSize()
Simon Glasseca32212018-06-01 09:38:12 -0600265
266 def _SortEntries(self):
Simon Glasse8561af2018-08-01 15:22:37 -0600267 """Sort entries by offset"""
268 entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
Simon Glasseca32212018-06-01 09:38:12 -0600269 self._entries.clear()
270 for entry in entries:
271 self._entries[entry._node.name] = entry
272
Simon Glassfa79a812018-09-14 04:57:29 -0600273 def _ExpandEntries(self):
274 """Expand any entries that are permitted to"""
275 exp_entry = None
276 for entry in self._entries.values():
277 if exp_entry:
278 exp_entry.ExpandToLimit(entry.offset)
279 exp_entry = None
280 if entry.expand_size:
281 exp_entry = entry
282 if exp_entry:
283 exp_entry.ExpandToLimit(self._size)
284
Simon Glasseca32212018-06-01 09:38:12 -0600285 def CheckEntries(self):
Simon Glassfa79a812018-09-14 04:57:29 -0600286 """Check that entries do not overlap or extend outside the section
287
288 This also sorts entries, if needed and expands
289 """
Simon Glasseca32212018-06-01 09:38:12 -0600290 if self._sort:
291 self._SortEntries()
Simon Glassfa79a812018-09-14 04:57:29 -0600292 self._ExpandEntries()
Simon Glasse8561af2018-08-01 15:22:37 -0600293 offset = 0
Simon Glasseca32212018-06-01 09:38:12 -0600294 prev_name = 'None'
295 for entry in self._entries.values():
Simon Glasse8561af2018-08-01 15:22:37 -0600296 entry.CheckOffset()
297 if (entry.offset < self._skip_at_start or
Simon Glass2a7c4b62018-09-14 04:57:27 -0600298 entry.offset + entry.size > self._skip_at_start + self._size):
Simon Glasse8561af2018-08-01 15:22:37 -0600299 entry.Raise("Offset %#x (%d) is outside the section starting "
Simon Glasseca32212018-06-01 09:38:12 -0600300 "at %#x (%d)" %
Simon Glasse8561af2018-08-01 15:22:37 -0600301 (entry.offset, entry.offset, self._skip_at_start,
Simon Glasseca32212018-06-01 09:38:12 -0600302 self._skip_at_start))
Simon Glasse8561af2018-08-01 15:22:37 -0600303 if entry.offset < offset:
304 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
Simon Glasseca32212018-06-01 09:38:12 -0600305 "ending at %#x (%d)" %
Simon Glasse8561af2018-08-01 15:22:37 -0600306 (entry.offset, entry.offset, prev_name, offset, offset))
307 offset = entry.offset + entry.size
Simon Glasseca32212018-06-01 09:38:12 -0600308 prev_name = entry.GetPath()
309
Simon Glass9dcc8612018-08-01 15:22:42 -0600310 def SetImagePos(self, image_pos):
311 self._image_pos = image_pos
312 for entry in self._entries.values():
313 entry.SetImagePos(image_pos)
314
Simon Glasseca32212018-06-01 09:38:12 -0600315 def ProcessEntryContents(self):
316 """Call the ProcessContents() method for each entry
317
318 This is intended to adjust the contents as needed by the entry type.
319 """
320 for entry in self._entries.values():
321 entry.ProcessContents()
322
323 def WriteSymbols(self):
324 """Write symbol values into binary files for access at run time"""
325 for entry in self._entries.values():
326 entry.WriteSymbols(self)
327
Simon Glasse8561af2018-08-01 15:22:37 -0600328 def BuildSection(self, fd, base_offset):
Simon Glasseca32212018-06-01 09:38:12 -0600329 """Write the section to a file"""
Simon Glasse8561af2018-08-01 15:22:37 -0600330 fd.seek(base_offset)
Simon Glasseca32212018-06-01 09:38:12 -0600331 fd.write(self.GetData())
332
333 def GetData(self):
Simon Glass3a9a2b82018-07-17 13:25:28 -0600334 """Get the contents of the section"""
Simon Glassac0d4952019-05-14 15:53:47 -0600335 section_data = tools.GetBytes(self._pad_byte, self._size)
Simon Glasseca32212018-06-01 09:38:12 -0600336
337 for entry in self._entries.values():
338 data = entry.GetData()
Simon Glasse8561af2018-08-01 15:22:37 -0600339 base = self._pad_before + entry.offset - self._skip_at_start
Simon Glasseca32212018-06-01 09:38:12 -0600340 section_data = (section_data[:base] + data +
341 section_data[base + len(data):])
342 return section_data
343
344 def LookupSymbol(self, sym_name, optional, msg):
345 """Look up a symbol in an ELF file
346
347 Looks up a symbol in an ELF file. Only entry types which come from an
348 ELF image can be used by this function.
349
Simon Glasse8561af2018-08-01 15:22:37 -0600350 At present the only entry property supported is offset.
Simon Glasseca32212018-06-01 09:38:12 -0600351
352 Args:
353 sym_name: Symbol name in the ELF file to look up in the format
354 _binman_<entry>_prop_<property> where <entry> is the name of
355 the entry and <property> is the property to find (e.g.
Simon Glasse8561af2018-08-01 15:22:37 -0600356 _binman_u_boot_prop_offset). As a special case, you can append
Simon Glasseca32212018-06-01 09:38:12 -0600357 _any to <entry> to have it search for any matching entry. E.g.
Simon Glasse8561af2018-08-01 15:22:37 -0600358 _binman_u_boot_any_prop_offset will match entries called u-boot,
Simon Glasseca32212018-06-01 09:38:12 -0600359 u-boot-img and u-boot-nodtb)
360 optional: True if the symbol is optional. If False this function
361 will raise if the symbol is not found
362 msg: Message to display if an error occurs
363
364 Returns:
365 Value that should be assigned to that symbol, or None if it was
366 optional and not found
367
368 Raises:
369 ValueError if the symbol is invalid or not found, or references a
370 property which is not supported
371 """
372 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
373 if not m:
374 raise ValueError("%s: Symbol '%s' has invalid format" %
375 (msg, sym_name))
376 entry_name, prop_name = m.groups()
377 entry_name = entry_name.replace('_', '-')
378 entry = self._entries.get(entry_name)
379 if not entry:
380 if entry_name.endswith('-any'):
381 root = entry_name[:-4]
382 for name in self._entries:
383 if name.startswith(root):
384 rest = name[len(root):]
385 if rest in ['', '-img', '-nodtb']:
386 entry = self._entries[name]
387 if not entry:
388 err = ("%s: Entry '%s' not found in list (%s)" %
389 (msg, entry_name, ','.join(self._entries.keys())))
390 if optional:
391 print('Warning: %s' % err, file=sys.stderr)
392 return None
393 raise ValueError(err)
Simon Glasse8561af2018-08-01 15:22:37 -0600394 if prop_name == 'offset':
395 return entry.offset
Simon Glass9dcc8612018-08-01 15:22:42 -0600396 elif prop_name == 'image_pos':
397 return entry.image_pos
Simon Glasseca32212018-06-01 09:38:12 -0600398 else:
399 raise ValueError("%s: No such property '%s'" % (msg, prop_name))
400
401 def GetEntries(self):
Simon Glass3a9a2b82018-07-17 13:25:28 -0600402 """Get the number of entries in a section
403
404 Returns:
405 Number of entries in a section
406 """
Simon Glasseca32212018-06-01 09:38:12 -0600407 return self._entries
Simon Glass30732662018-06-01 09:38:20 -0600408
Simon Glass3a9a2b82018-07-17 13:25:28 -0600409 def GetSize(self):
410 """Get the size of a section in bytes
411
412 This is only meaningful if the section has a pre-defined size, or the
413 entries within it have been packed, so that the size has been
414 calculated.
415
416 Returns:
417 Entry size in bytes
418 """
419 return self._size
420
Simon Glass30732662018-06-01 09:38:20 -0600421 def WriteMap(self, fd, indent):
422 """Write a map of the section to a .map file
423
424 Args:
425 fd: File to write the map to
426 """
Simon Glasseb023b32019-04-25 21:58:39 -0600427 Entry.WriteMapLine(fd, indent, self._name, self._offset or 0,
428 self._size, self._image_pos)
Simon Glass30732662018-06-01 09:38:20 -0600429 for entry in self._entries.values():
Simon Glass3a9a2b82018-07-17 13:25:28 -0600430 entry.WriteMap(fd, indent + 1)
Simon Glass5c350162018-07-17 13:25:47 -0600431
432 def GetContentsByPhandle(self, phandle, source_entry):
433 """Get the data contents of an entry specified by a phandle
434
435 This uses a phandle to look up a node and and find the entry
436 associated with it. Then it returnst he contents of that entry.
437
438 Args:
439 phandle: Phandle to look up (integer)
440 source_entry: Entry containing that phandle (used for error
441 reporting)
442
443 Returns:
444 data from associated entry (as a string), or None if not found
445 """
446 node = self._node.GetFdt().LookupPhandle(phandle)
447 if not node:
448 source_entry.Raise("Cannot find node for phandle %d" % phandle)
449 for entry in self._entries.values():
450 if entry._node == node:
Simon Glassc64aea52018-09-14 04:57:34 -0600451 return entry.GetData()
Simon Glass5c350162018-07-17 13:25:47 -0600452 source_entry.Raise("Cannot find entry for node '%s'" % node.name)
Simon Glassfa79a812018-09-14 04:57:29 -0600453
454 def ExpandSize(self, size):
455 if size != self._size:
456 self._size = size
Simon Glassc64aea52018-09-14 04:57:34 -0600457
458 def GetRootSkipAtStart(self):
459 if self._parent_section:
460 return self._parent_section.GetRootSkipAtStart()
461 return self._skip_at_start
462
463 def GetImageSize(self):
464 return self._image._size