blob: 0ba542ee987a3b012e587c157a1289b4ef3ea447 [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
Simon Glass0c9d5b52018-09-14 04:57:22 -060011from sets import Set
Simon Glasseca32212018-06-01 09:38:12 -060012import sys
13
14import fdt_util
15import re
Simon Glassc8135dc2018-09-14 04:57:21 -060016import state
Simon Glasseca32212018-06-01 09:38:12 -060017import tools
18
19class Section(object):
20 """A section which contains multiple entries
21
22 A section represents a collection of entries. There must be one or more
23 sections in an image. Sections are used to group entries together.
24
25 Attributes:
26 _node: Node object that contains the section definition in device tree
Simon Glass7c767ad2018-09-14 04:57:33 -060027 _parent_section: Parent Section object which created this Section
Simon Glasseca32212018-06-01 09:38:12 -060028 _size: Section size in bytes, or None if not known yet
29 _align_size: Section size alignment, or None
30 _pad_before: Number of bytes before the first entry starts. This
Simon Glasse8561af2018-08-01 15:22:37 -060031 effectively changes the place where entry offset 0 starts
Simon Glasseca32212018-06-01 09:38:12 -060032 _pad_after: Number of bytes after the last entry ends. The last
33 entry will finish on or before this boundary
34 _pad_byte: Byte to use to pad the section where there is no entry
Simon Glasse8561af2018-08-01 15:22:37 -060035 _sort: True if entries should be sorted by offset, False if they
Simon Glasseca32212018-06-01 09:38:12 -060036 must be in-order in the device tree description
37 _skip_at_start: Number of bytes before the first entry starts. These
Simon Glasse8561af2018-08-01 15:22:37 -060038 effectively adjust the starting offset of entries. For example,
Simon Glasseca32212018-06-01 09:38:12 -060039 if _pad_before is 16, then the first entry would start at 16.
Simon Glasse8561af2018-08-01 15:22:37 -060040 An entry with offset = 20 would in fact be written at offset 4
Simon Glasseca32212018-06-01 09:38:12 -060041 in the image file.
42 _end_4gb: Indicates that the section ends at the 4GB boundary. This is
Simon Glasse8561af2018-08-01 15:22:37 -060043 used for x86 images, which want to use offsets such that a memory
44 address (like 0xff800000) is the first entry offset. This causes
45 _skip_at_start to be set to the starting memory address.
Simon Glass3b78d532018-06-01 09:38:21 -060046 _name_prefix: Prefix to add to the name of all entries within this
47 section
Simon Glasseca32212018-06-01 09:38:12 -060048 _entries: OrderedDict() of entries
49 """
Simon Glass7c767ad2018-09-14 04:57:33 -060050 def __init__(self, name, parent_section, node, image, test=False):
Simon Glasseca32212018-06-01 09:38:12 -060051 global entry
52 global Entry
53 import entry
54 from entry import Entry
55
Simon Glass7c767ad2018-09-14 04:57:33 -060056 self._parent_section = parent_section
Simon Glass3a9a2b82018-07-17 13:25:28 -060057 self._name = name
Simon Glasseca32212018-06-01 09:38:12 -060058 self._node = node
Simon Glass7c767ad2018-09-14 04:57:33 -060059 self._image = image
Simon Glasseb023b32019-04-25 21:58:39 -060060 self._offset = None
Simon Glasseca32212018-06-01 09:38:12 -060061 self._size = None
62 self._align_size = None
63 self._pad_before = 0
64 self._pad_after = 0
65 self._pad_byte = 0
66 self._sort = False
Jagdish Gediya0fb978c2018-09-03 21:35:07 +053067 self._skip_at_start = None
Simon Glasseca32212018-06-01 09:38:12 -060068 self._end_4gb = False
Simon Glass3b78d532018-06-01 09:38:21 -060069 self._name_prefix = ''
Simon Glasseca32212018-06-01 09:38:12 -060070 self._entries = OrderedDict()
Simon Glassc64aea52018-09-14 04:57:34 -060071 self._image_pos = None
Simon Glasseca32212018-06-01 09:38:12 -060072 if not test:
73 self._ReadNode()
74 self._ReadEntries()
75
76 def _ReadNode(self):
77 """Read properties from the section node"""
Simon Glasseb023b32019-04-25 21:58:39 -060078 self._offset = fdt_util.GetInt(self._node, 'offset')
Simon Glasseca32212018-06-01 09:38:12 -060079 self._size = fdt_util.GetInt(self._node, 'size')
80 self._align_size = fdt_util.GetInt(self._node, 'align-size')
81 if tools.NotPowerOfTwo(self._align_size):
82 self._Raise("Alignment size %s must be a power of two" %
83 self._align_size)
84 self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
85 self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
86 self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
Simon Glasse8561af2018-08-01 15:22:37 -060087 self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
Simon Glasseca32212018-06-01 09:38:12 -060088 self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
Jagdish Gediya0fb978c2018-09-03 21:35:07 +053089 self._skip_at_start = fdt_util.GetInt(self._node, 'skip-at-start')
Simon Glasseca32212018-06-01 09:38:12 -060090 if self._end_4gb:
Jagdish Gediya0fb978c2018-09-03 21:35:07 +053091 if not self._size:
92 self._Raise("Section size must be provided when using end-at-4gb")
93 if self._skip_at_start is not None:
94 self._Raise("Provide either 'end-at-4gb' or 'skip-at-start'")
95 else:
96 self._skip_at_start = 0x100000000 - self._size
97 else:
98 if self._skip_at_start is None:
99 self._skip_at_start = 0
Simon Glass3b78d532018-06-01 09:38:21 -0600100 self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
Simon Glasseca32212018-06-01 09:38:12 -0600101
102 def _ReadEntries(self):
103 for node in self._node.subnodes:
Simon Glassae7cf032018-09-14 04:57:31 -0600104 if node.name == 'hash':
105 continue
Simon Glass3b78d532018-06-01 09:38:21 -0600106 entry = Entry.Create(self, node)
107 entry.SetPrefix(self._name_prefix)
108 self._entries[node.name] = entry
Simon Glasseca32212018-06-01 09:38:12 -0600109
Simon Glass0c9d5b52018-09-14 04:57:22 -0600110 def GetFdtSet(self):
111 """Get the set of device tree files used by this image"""
112 fdt_set = Set()
113 for entry in self._entries.values():
114 fdt_set.update(entry.GetFdtSet())
115 return fdt_set
116
Simon Glass3a9a2b82018-07-17 13:25:28 -0600117 def SetOffset(self, offset):
118 self._offset = offset
119
Simon Glassac6328c2018-09-14 04:57:28 -0600120 def ExpandEntries(self):
121 for entry in self._entries.values():
122 entry.ExpandEntries()
123
Simon Glasse22f8fa2018-07-06 10:27:41 -0600124 def AddMissingProperties(self):
Simon Glass3a9a2b82018-07-17 13:25:28 -0600125 """Add new properties to the device tree as needed for this entry"""
Simon Glass9dcc8612018-08-01 15:22:42 -0600126 for prop in ['offset', 'size', 'image-pos']:
Simon Glass3a9a2b82018-07-17 13:25:28 -0600127 if not prop in self._node.props:
Simon Glassc8135dc2018-09-14 04:57:21 -0600128 state.AddZeroProp(self._node, prop)
Simon Glassae7cf032018-09-14 04:57:31 -0600129 state.CheckAddHashProp(self._node)
Simon Glasse22f8fa2018-07-06 10:27:41 -0600130 for entry in self._entries.values():
131 entry.AddMissingProperties()
132
133 def SetCalculatedProperties(self):
Simon Glasseb023b32019-04-25 21:58:39 -0600134 state.SetInt(self._node, 'offset', self._offset or 0)
Simon Glassc8135dc2018-09-14 04:57:21 -0600135 state.SetInt(self._node, 'size', self._size)
Simon Glassc64aea52018-09-14 04:57:34 -0600136 image_pos = self._image_pos
137 if self._parent_section:
138 image_pos -= self._parent_section.GetRootSkipAtStart()
139 state.SetInt(self._node, 'image-pos', image_pos)
Simon Glasse22f8fa2018-07-06 10:27:41 -0600140 for entry in self._entries.values():
141 entry.SetCalculatedProperties()
142
Simon Glass92307732018-07-06 10:27:40 -0600143 def ProcessFdt(self, fdt):
144 todo = self._entries.values()
145 for passnum in range(3):
146 next_todo = []
147 for entry in todo:
148 if not entry.ProcessFdt(fdt):
149 next_todo.append(entry)
150 todo = next_todo
151 if not todo:
152 break
153 if todo:
154 self._Raise('Internal error: Could not complete processing of Fdt: '
155 'remaining %s' % todo)
156 return True
157
Simon Glasseca32212018-06-01 09:38:12 -0600158 def CheckSize(self):
159 """Check that the section contents does not exceed its size, etc."""
160 contents_size = 0
161 for entry in self._entries.values():
Simon Glasse8561af2018-08-01 15:22:37 -0600162 contents_size = max(contents_size, entry.offset + entry.size)
Simon Glasseca32212018-06-01 09:38:12 -0600163
164 contents_size -= self._skip_at_start
165
166 size = self._size
167 if not size:
168 size = self._pad_before + contents_size + self._pad_after
169 size = tools.Align(size, self._align_size)
170
171 if self._size and contents_size > self._size:
172 self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
173 (contents_size, contents_size, self._size, self._size))
174 if not self._size:
175 self._size = size
176 if self._size != tools.Align(self._size, self._align_size):
177 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
178 (self._size, self._size, self._align_size, self._align_size))
179 return size
180
181 def _Raise(self, msg):
182 """Raises an error for this section
183
184 Args:
185 msg: Error message to use in the raise string
186 Raises:
187 ValueError()
188 """
189 raise ValueError("Section '%s': %s" % (self._node.path, msg))
190
191 def GetPath(self):
192 """Get the path of an image (in the FDT)
193
194 Returns:
195 Full path of the node for this image
196 """
197 return self._node.path
198
199 def FindEntryType(self, etype):
200 """Find an entry type in the section
201
202 Args:
203 etype: Entry type to find
204 Returns:
205 entry matching that type, or None if not found
206 """
207 for entry in self._entries.values():
208 if entry.etype == etype:
209 return entry
210 return None
211
212 def GetEntryContents(self):
213 """Call ObtainContents() for each entry
214
215 This calls each entry's ObtainContents() a few times until they all
216 return True. We stop calling an entry's function once it returns
217 True. This allows the contents of one entry to depend on another.
218
219 After 3 rounds we give up since it's likely an error.
220 """
221 todo = self._entries.values()
222 for passnum in range(3):
223 next_todo = []
224 for entry in todo:
225 if not entry.ObtainContents():
226 next_todo.append(entry)
227 todo = next_todo
228 if not todo:
229 break
Simon Glass6ba679c2018-07-06 10:27:17 -0600230 if todo:
231 self._Raise('Internal error: Could not complete processing of '
232 'contents: remaining %s' % todo)
233 return True
Simon Glasseca32212018-06-01 09:38:12 -0600234
Simon Glasse8561af2018-08-01 15:22:37 -0600235 def _SetEntryOffsetSize(self, name, offset, size):
236 """Set the offset and size of an entry
Simon Glasseca32212018-06-01 09:38:12 -0600237
238 Args:
239 name: Entry name to update
Simon Glasse8561af2018-08-01 15:22:37 -0600240 offset: New offset
Simon Glasseca32212018-06-01 09:38:12 -0600241 size: New size
242 """
243 entry = self._entries.get(name)
244 if not entry:
Simon Glasse8561af2018-08-01 15:22:37 -0600245 self._Raise("Unable to set offset/size for unknown entry '%s'" %
246 name)
247 entry.SetOffsetSize(self._skip_at_start + offset, size)
Simon Glasseca32212018-06-01 09:38:12 -0600248
Simon Glasse8561af2018-08-01 15:22:37 -0600249 def GetEntryOffsets(self):
250 """Handle entries that want to set the offset/size of other entries
Simon Glasseca32212018-06-01 09:38:12 -0600251
Simon Glasse8561af2018-08-01 15:22:37 -0600252 This calls each entry's GetOffsets() method. If it returns a list
Simon Glasseca32212018-06-01 09:38:12 -0600253 of entries to update, it updates them.
254 """
255 for entry in self._entries.values():
Simon Glasse8561af2018-08-01 15:22:37 -0600256 offset_dict = entry.GetOffsets()
257 for name, info in offset_dict.iteritems():
258 self._SetEntryOffsetSize(name, *info)
Simon Glasseca32212018-06-01 09:38:12 -0600259
260 def PackEntries(self):
261 """Pack all entries into the section"""
Simon Glasse8561af2018-08-01 15:22:37 -0600262 offset = self._skip_at_start
Simon Glasseca32212018-06-01 09:38:12 -0600263 for entry in self._entries.values():
Simon Glasse8561af2018-08-01 15:22:37 -0600264 offset = entry.Pack(offset)
265 self._size = self.CheckSize()
Simon Glasseca32212018-06-01 09:38:12 -0600266
267 def _SortEntries(self):
Simon Glasse8561af2018-08-01 15:22:37 -0600268 """Sort entries by offset"""
269 entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
Simon Glasseca32212018-06-01 09:38:12 -0600270 self._entries.clear()
271 for entry in entries:
272 self._entries[entry._node.name] = entry
273
Simon Glassfa79a812018-09-14 04:57:29 -0600274 def _ExpandEntries(self):
275 """Expand any entries that are permitted to"""
276 exp_entry = None
277 for entry in self._entries.values():
278 if exp_entry:
279 exp_entry.ExpandToLimit(entry.offset)
280 exp_entry = None
281 if entry.expand_size:
282 exp_entry = entry
283 if exp_entry:
284 exp_entry.ExpandToLimit(self._size)
285
Simon Glasseca32212018-06-01 09:38:12 -0600286 def CheckEntries(self):
Simon Glassfa79a812018-09-14 04:57:29 -0600287 """Check that entries do not overlap or extend outside the section
288
289 This also sorts entries, if needed and expands
290 """
Simon Glasseca32212018-06-01 09:38:12 -0600291 if self._sort:
292 self._SortEntries()
Simon Glassfa79a812018-09-14 04:57:29 -0600293 self._ExpandEntries()
Simon Glasse8561af2018-08-01 15:22:37 -0600294 offset = 0
Simon Glasseca32212018-06-01 09:38:12 -0600295 prev_name = 'None'
296 for entry in self._entries.values():
Simon Glasse8561af2018-08-01 15:22:37 -0600297 entry.CheckOffset()
298 if (entry.offset < self._skip_at_start or
Simon Glass2a7c4b62018-09-14 04:57:27 -0600299 entry.offset + entry.size > self._skip_at_start + self._size):
Simon Glasse8561af2018-08-01 15:22:37 -0600300 entry.Raise("Offset %#x (%d) is outside the section starting "
Simon Glasseca32212018-06-01 09:38:12 -0600301 "at %#x (%d)" %
Simon Glasse8561af2018-08-01 15:22:37 -0600302 (entry.offset, entry.offset, self._skip_at_start,
Simon Glasseca32212018-06-01 09:38:12 -0600303 self._skip_at_start))
Simon Glasse8561af2018-08-01 15:22:37 -0600304 if entry.offset < offset:
305 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
Simon Glasseca32212018-06-01 09:38:12 -0600306 "ending at %#x (%d)" %
Simon Glasse8561af2018-08-01 15:22:37 -0600307 (entry.offset, entry.offset, prev_name, offset, offset))
308 offset = entry.offset + entry.size
Simon Glasseca32212018-06-01 09:38:12 -0600309 prev_name = entry.GetPath()
310
Simon Glass9dcc8612018-08-01 15:22:42 -0600311 def SetImagePos(self, image_pos):
312 self._image_pos = image_pos
313 for entry in self._entries.values():
314 entry.SetImagePos(image_pos)
315
Simon Glasseca32212018-06-01 09:38:12 -0600316 def ProcessEntryContents(self):
317 """Call the ProcessContents() method for each entry
318
319 This is intended to adjust the contents as needed by the entry type.
320 """
321 for entry in self._entries.values():
322 entry.ProcessContents()
323
324 def WriteSymbols(self):
325 """Write symbol values into binary files for access at run time"""
326 for entry in self._entries.values():
327 entry.WriteSymbols(self)
328
Simon Glasse8561af2018-08-01 15:22:37 -0600329 def BuildSection(self, fd, base_offset):
Simon Glasseca32212018-06-01 09:38:12 -0600330 """Write the section to a file"""
Simon Glasse8561af2018-08-01 15:22:37 -0600331 fd.seek(base_offset)
Simon Glasseca32212018-06-01 09:38:12 -0600332 fd.write(self.GetData())
333
334 def GetData(self):
Simon Glass3a9a2b82018-07-17 13:25:28 -0600335 """Get the contents of the section"""
Simon Glasseca32212018-06-01 09:38:12 -0600336 section_data = chr(self._pad_byte) * self._size
337
338 for entry in self._entries.values():
339 data = entry.GetData()
Simon Glasse8561af2018-08-01 15:22:37 -0600340 base = self._pad_before + entry.offset - self._skip_at_start
Simon Glasseca32212018-06-01 09:38:12 -0600341 section_data = (section_data[:base] + data +
342 section_data[base + len(data):])
343 return section_data
344
345 def LookupSymbol(self, sym_name, optional, msg):
346 """Look up a symbol in an ELF file
347
348 Looks up a symbol in an ELF file. Only entry types which come from an
349 ELF image can be used by this function.
350
Simon Glasse8561af2018-08-01 15:22:37 -0600351 At present the only entry property supported is offset.
Simon Glasseca32212018-06-01 09:38:12 -0600352
353 Args:
354 sym_name: Symbol name in the ELF file to look up in the format
355 _binman_<entry>_prop_<property> where <entry> is the name of
356 the entry and <property> is the property to find (e.g.
Simon Glasse8561af2018-08-01 15:22:37 -0600357 _binman_u_boot_prop_offset). As a special case, you can append
Simon Glasseca32212018-06-01 09:38:12 -0600358 _any to <entry> to have it search for any matching entry. E.g.
Simon Glasse8561af2018-08-01 15:22:37 -0600359 _binman_u_boot_any_prop_offset will match entries called u-boot,
Simon Glasseca32212018-06-01 09:38:12 -0600360 u-boot-img and u-boot-nodtb)
361 optional: True if the symbol is optional. If False this function
362 will raise if the symbol is not found
363 msg: Message to display if an error occurs
364
365 Returns:
366 Value that should be assigned to that symbol, or None if it was
367 optional and not found
368
369 Raises:
370 ValueError if the symbol is invalid or not found, or references a
371 property which is not supported
372 """
373 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
374 if not m:
375 raise ValueError("%s: Symbol '%s' has invalid format" %
376 (msg, sym_name))
377 entry_name, prop_name = m.groups()
378 entry_name = entry_name.replace('_', '-')
379 entry = self._entries.get(entry_name)
380 if not entry:
381 if entry_name.endswith('-any'):
382 root = entry_name[:-4]
383 for name in self._entries:
384 if name.startswith(root):
385 rest = name[len(root):]
386 if rest in ['', '-img', '-nodtb']:
387 entry = self._entries[name]
388 if not entry:
389 err = ("%s: Entry '%s' not found in list (%s)" %
390 (msg, entry_name, ','.join(self._entries.keys())))
391 if optional:
392 print('Warning: %s' % err, file=sys.stderr)
393 return None
394 raise ValueError(err)
Simon Glasse8561af2018-08-01 15:22:37 -0600395 if prop_name == 'offset':
396 return entry.offset
Simon Glass9dcc8612018-08-01 15:22:42 -0600397 elif prop_name == 'image_pos':
398 return entry.image_pos
Simon Glasseca32212018-06-01 09:38:12 -0600399 else:
400 raise ValueError("%s: No such property '%s'" % (msg, prop_name))
401
402 def GetEntries(self):
Simon Glass3a9a2b82018-07-17 13:25:28 -0600403 """Get the number of entries in a section
404
405 Returns:
406 Number of entries in a section
407 """
Simon Glasseca32212018-06-01 09:38:12 -0600408 return self._entries
Simon Glass30732662018-06-01 09:38:20 -0600409
Simon Glass3a9a2b82018-07-17 13:25:28 -0600410 def GetSize(self):
411 """Get the size of a section in bytes
412
413 This is only meaningful if the section has a pre-defined size, or the
414 entries within it have been packed, so that the size has been
415 calculated.
416
417 Returns:
418 Entry size in bytes
419 """
420 return self._size
421
Simon Glass30732662018-06-01 09:38:20 -0600422 def WriteMap(self, fd, indent):
423 """Write a map of the section to a .map file
424
425 Args:
426 fd: File to write the map to
427 """
Simon Glasseb023b32019-04-25 21:58:39 -0600428 Entry.WriteMapLine(fd, indent, self._name, self._offset or 0,
429 self._size, self._image_pos)
Simon Glass30732662018-06-01 09:38:20 -0600430 for entry in self._entries.values():
Simon Glass3a9a2b82018-07-17 13:25:28 -0600431 entry.WriteMap(fd, indent + 1)
Simon Glass5c350162018-07-17 13:25:47 -0600432
433 def GetContentsByPhandle(self, phandle, source_entry):
434 """Get the data contents of an entry specified by a phandle
435
436 This uses a phandle to look up a node and and find the entry
437 associated with it. Then it returnst he contents of that entry.
438
439 Args:
440 phandle: Phandle to look up (integer)
441 source_entry: Entry containing that phandle (used for error
442 reporting)
443
444 Returns:
445 data from associated entry (as a string), or None if not found
446 """
447 node = self._node.GetFdt().LookupPhandle(phandle)
448 if not node:
449 source_entry.Raise("Cannot find node for phandle %d" % phandle)
450 for entry in self._entries.values():
451 if entry._node == node:
Simon Glassc64aea52018-09-14 04:57:34 -0600452 return entry.GetData()
Simon Glass5c350162018-07-17 13:25:47 -0600453 source_entry.Raise("Cannot find entry for node '%s'" % node.name)
Simon Glassfa79a812018-09-14 04:57:29 -0600454
455 def ExpandSize(self, size):
456 if size != self._size:
457 self._size = size
Simon Glassc64aea52018-09-14 04:57:34 -0600458
459 def GetRootSkipAtStart(self):
460 if self._parent_section:
461 return self._parent_section.GetRootSkipAtStart()
462 return self._skip_at_start
463
464 def GetImageSize(self):
465 return self._image._size