blob: d78a25e83d0bc6466fdf5808f56f0ab1732ee014 [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
15import tools
16
17class Section(object):
18 """A section which contains multiple entries
19
20 A section represents a collection of entries. There must be one or more
21 sections in an image. Sections are used to group entries together.
22
23 Attributes:
24 _node: Node object that contains the section definition in device tree
25 _size: Section size in bytes, or None if not known yet
26 _align_size: Section size alignment, or None
27 _pad_before: Number of bytes before the first entry starts. This
Simon Glasse8561af2018-08-01 15:22:37 -060028 effectively changes the place where entry offset 0 starts
Simon Glasseca32212018-06-01 09:38:12 -060029 _pad_after: Number of bytes after the last entry ends. The last
30 entry will finish on or before this boundary
31 _pad_byte: Byte to use to pad the section where there is no entry
Simon Glasse8561af2018-08-01 15:22:37 -060032 _sort: True if entries should be sorted by offset, False if they
Simon Glasseca32212018-06-01 09:38:12 -060033 must be in-order in the device tree description
34 _skip_at_start: Number of bytes before the first entry starts. These
Simon Glasse8561af2018-08-01 15:22:37 -060035 effectively adjust the starting offset of entries. For example,
Simon Glasseca32212018-06-01 09:38:12 -060036 if _pad_before is 16, then the first entry would start at 16.
Simon Glasse8561af2018-08-01 15:22:37 -060037 An entry with offset = 20 would in fact be written at offset 4
Simon Glasseca32212018-06-01 09:38:12 -060038 in the image file.
39 _end_4gb: Indicates that the section ends at the 4GB boundary. This is
Simon Glasse8561af2018-08-01 15:22:37 -060040 used for x86 images, which want to use offsets such that a memory
41 address (like 0xff800000) is the first entry offset. This causes
42 _skip_at_start to be set to the starting memory address.
Simon Glass3b78d532018-06-01 09:38:21 -060043 _name_prefix: Prefix to add to the name of all entries within this
44 section
Simon Glasseca32212018-06-01 09:38:12 -060045 _entries: OrderedDict() of entries
46 """
47 def __init__(self, name, node, test=False):
48 global entry
49 global Entry
50 import entry
51 from entry import Entry
52
53 self._node = node
Simon Glasse8561af2018-08-01 15:22:37 -060054 self._offset = 0
Simon Glasseca32212018-06-01 09:38:12 -060055 self._size = None
56 self._align_size = None
57 self._pad_before = 0
58 self._pad_after = 0
59 self._pad_byte = 0
60 self._sort = False
61 self._skip_at_start = 0
62 self._end_4gb = False
Simon Glass3b78d532018-06-01 09:38:21 -060063 self._name_prefix = ''
Simon Glasseca32212018-06-01 09:38:12 -060064 self._entries = OrderedDict()
65 if not test:
66 self._ReadNode()
67 self._ReadEntries()
68
69 def _ReadNode(self):
70 """Read properties from the section node"""
71 self._size = fdt_util.GetInt(self._node, 'size')
72 self._align_size = fdt_util.GetInt(self._node, 'align-size')
73 if tools.NotPowerOfTwo(self._align_size):
74 self._Raise("Alignment size %s must be a power of two" %
75 self._align_size)
76 self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
77 self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
78 self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
Simon Glasse8561af2018-08-01 15:22:37 -060079 self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
Simon Glasseca32212018-06-01 09:38:12 -060080 self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
81 if self._end_4gb and not self._size:
82 self._Raise("Section size must be provided when using end-at-4gb")
83 if self._end_4gb:
84 self._skip_at_start = 0x100000000 - self._size
Simon Glass3b78d532018-06-01 09:38:21 -060085 self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
Simon Glasseca32212018-06-01 09:38:12 -060086
87 def _ReadEntries(self):
88 for node in self._node.subnodes:
Simon Glass3b78d532018-06-01 09:38:21 -060089 entry = Entry.Create(self, node)
90 entry.SetPrefix(self._name_prefix)
91 self._entries[node.name] = entry
Simon Glasseca32212018-06-01 09:38:12 -060092
Simon Glasse22f8fa2018-07-06 10:27:41 -060093 def AddMissingProperties(self):
94 for entry in self._entries.values():
95 entry.AddMissingProperties()
96
97 def SetCalculatedProperties(self):
98 for entry in self._entries.values():
99 entry.SetCalculatedProperties()
100
Simon Glass92307732018-07-06 10:27:40 -0600101 def ProcessFdt(self, fdt):
102 todo = self._entries.values()
103 for passnum in range(3):
104 next_todo = []
105 for entry in todo:
106 if not entry.ProcessFdt(fdt):
107 next_todo.append(entry)
108 todo = next_todo
109 if not todo:
110 break
111 if todo:
112 self._Raise('Internal error: Could not complete processing of Fdt: '
113 'remaining %s' % todo)
114 return True
115
Simon Glasseca32212018-06-01 09:38:12 -0600116 def CheckSize(self):
117 """Check that the section contents does not exceed its size, etc."""
118 contents_size = 0
119 for entry in self._entries.values():
Simon Glasse8561af2018-08-01 15:22:37 -0600120 contents_size = max(contents_size, entry.offset + entry.size)
Simon Glasseca32212018-06-01 09:38:12 -0600121
122 contents_size -= self._skip_at_start
123
124 size = self._size
125 if not size:
126 size = self._pad_before + contents_size + self._pad_after
127 size = tools.Align(size, self._align_size)
128
129 if self._size and contents_size > self._size:
130 self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
131 (contents_size, contents_size, self._size, self._size))
132 if not self._size:
133 self._size = size
134 if self._size != tools.Align(self._size, self._align_size):
135 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
136 (self._size, self._size, self._align_size, self._align_size))
137 return size
138
139 def _Raise(self, msg):
140 """Raises an error for this section
141
142 Args:
143 msg: Error message to use in the raise string
144 Raises:
145 ValueError()
146 """
147 raise ValueError("Section '%s': %s" % (self._node.path, msg))
148
149 def GetPath(self):
150 """Get the path of an image (in the FDT)
151
152 Returns:
153 Full path of the node for this image
154 """
155 return self._node.path
156
157 def FindEntryType(self, etype):
158 """Find an entry type in the section
159
160 Args:
161 etype: Entry type to find
162 Returns:
163 entry matching that type, or None if not found
164 """
165 for entry in self._entries.values():
166 if entry.etype == etype:
167 return entry
168 return None
169
170 def GetEntryContents(self):
171 """Call ObtainContents() for each entry
172
173 This calls each entry's ObtainContents() a few times until they all
174 return True. We stop calling an entry's function once it returns
175 True. This allows the contents of one entry to depend on another.
176
177 After 3 rounds we give up since it's likely an error.
178 """
179 todo = self._entries.values()
180 for passnum in range(3):
181 next_todo = []
182 for entry in todo:
183 if not entry.ObtainContents():
184 next_todo.append(entry)
185 todo = next_todo
186 if not todo:
187 break
Simon Glass6ba679c2018-07-06 10:27:17 -0600188 if todo:
189 self._Raise('Internal error: Could not complete processing of '
190 'contents: remaining %s' % todo)
191 return True
Simon Glasseca32212018-06-01 09:38:12 -0600192
Simon Glasse8561af2018-08-01 15:22:37 -0600193 def _SetEntryOffsetSize(self, name, offset, size):
194 """Set the offset and size of an entry
Simon Glasseca32212018-06-01 09:38:12 -0600195
196 Args:
197 name: Entry name to update
Simon Glasse8561af2018-08-01 15:22:37 -0600198 offset: New offset
Simon Glasseca32212018-06-01 09:38:12 -0600199 size: New size
200 """
201 entry = self._entries.get(name)
202 if not entry:
Simon Glasse8561af2018-08-01 15:22:37 -0600203 self._Raise("Unable to set offset/size for unknown entry '%s'" %
204 name)
205 entry.SetOffsetSize(self._skip_at_start + offset, size)
Simon Glasseca32212018-06-01 09:38:12 -0600206
Simon Glasse8561af2018-08-01 15:22:37 -0600207 def GetEntryOffsets(self):
208 """Handle entries that want to set the offset/size of other entries
Simon Glasseca32212018-06-01 09:38:12 -0600209
Simon Glasse8561af2018-08-01 15:22:37 -0600210 This calls each entry's GetOffsets() method. If it returns a list
Simon Glasseca32212018-06-01 09:38:12 -0600211 of entries to update, it updates them.
212 """
213 for entry in self._entries.values():
Simon Glasse8561af2018-08-01 15:22:37 -0600214 offset_dict = entry.GetOffsets()
215 for name, info in offset_dict.iteritems():
216 self._SetEntryOffsetSize(name, *info)
Simon Glasseca32212018-06-01 09:38:12 -0600217
218 def PackEntries(self):
219 """Pack all entries into the section"""
Simon Glasse8561af2018-08-01 15:22:37 -0600220 offset = self._skip_at_start
Simon Glasseca32212018-06-01 09:38:12 -0600221 for entry in self._entries.values():
Simon Glasse8561af2018-08-01 15:22:37 -0600222 offset = entry.Pack(offset)
223 self._size = self.CheckSize()
Simon Glasseca32212018-06-01 09:38:12 -0600224
225 def _SortEntries(self):
Simon Glasse8561af2018-08-01 15:22:37 -0600226 """Sort entries by offset"""
227 entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
Simon Glasseca32212018-06-01 09:38:12 -0600228 self._entries.clear()
229 for entry in entries:
230 self._entries[entry._node.name] = entry
231
232 def CheckEntries(self):
233 """Check that entries do not overlap or extend outside the section"""
234 if self._sort:
235 self._SortEntries()
Simon Glasse8561af2018-08-01 15:22:37 -0600236 offset = 0
Simon Glasseca32212018-06-01 09:38:12 -0600237 prev_name = 'None'
238 for entry in self._entries.values():
Simon Glasse8561af2018-08-01 15:22:37 -0600239 entry.CheckOffset()
240 if (entry.offset < self._skip_at_start or
241 entry.offset >= self._skip_at_start + self._size):
242 entry.Raise("Offset %#x (%d) is outside the section starting "
Simon Glasseca32212018-06-01 09:38:12 -0600243 "at %#x (%d)" %
Simon Glasse8561af2018-08-01 15:22:37 -0600244 (entry.offset, entry.offset, self._skip_at_start,
Simon Glasseca32212018-06-01 09:38:12 -0600245 self._skip_at_start))
Simon Glasse8561af2018-08-01 15:22:37 -0600246 if entry.offset < offset:
247 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
Simon Glasseca32212018-06-01 09:38:12 -0600248 "ending at %#x (%d)" %
Simon Glasse8561af2018-08-01 15:22:37 -0600249 (entry.offset, entry.offset, prev_name, offset, offset))
250 offset = entry.offset + entry.size
Simon Glasseca32212018-06-01 09:38:12 -0600251 prev_name = entry.GetPath()
252
253 def ProcessEntryContents(self):
254 """Call the ProcessContents() method for each entry
255
256 This is intended to adjust the contents as needed by the entry type.
257 """
258 for entry in self._entries.values():
259 entry.ProcessContents()
260
261 def WriteSymbols(self):
262 """Write symbol values into binary files for access at run time"""
263 for entry in self._entries.values():
264 entry.WriteSymbols(self)
265
Simon Glasse8561af2018-08-01 15:22:37 -0600266 def BuildSection(self, fd, base_offset):
Simon Glasseca32212018-06-01 09:38:12 -0600267 """Write the section to a file"""
Simon Glasse8561af2018-08-01 15:22:37 -0600268 fd.seek(base_offset)
Simon Glasseca32212018-06-01 09:38:12 -0600269 fd.write(self.GetData())
270
271 def GetData(self):
272 """Write the section to a file"""
273 section_data = chr(self._pad_byte) * self._size
274
275 for entry in self._entries.values():
276 data = entry.GetData()
Simon Glasse8561af2018-08-01 15:22:37 -0600277 base = self._pad_before + entry.offset - self._skip_at_start
Simon Glasseca32212018-06-01 09:38:12 -0600278 section_data = (section_data[:base] + data +
279 section_data[base + len(data):])
280 return section_data
281
282 def LookupSymbol(self, sym_name, optional, msg):
283 """Look up a symbol in an ELF file
284
285 Looks up a symbol in an ELF file. Only entry types which come from an
286 ELF image can be used by this function.
287
Simon Glasse8561af2018-08-01 15:22:37 -0600288 At present the only entry property supported is offset.
Simon Glasseca32212018-06-01 09:38:12 -0600289
290 Args:
291 sym_name: Symbol name in the ELF file to look up in the format
292 _binman_<entry>_prop_<property> where <entry> is the name of
293 the entry and <property> is the property to find (e.g.
Simon Glasse8561af2018-08-01 15:22:37 -0600294 _binman_u_boot_prop_offset). As a special case, you can append
Simon Glasseca32212018-06-01 09:38:12 -0600295 _any to <entry> to have it search for any matching entry. E.g.
Simon Glasse8561af2018-08-01 15:22:37 -0600296 _binman_u_boot_any_prop_offset will match entries called u-boot,
Simon Glasseca32212018-06-01 09:38:12 -0600297 u-boot-img and u-boot-nodtb)
298 optional: True if the symbol is optional. If False this function
299 will raise if the symbol is not found
300 msg: Message to display if an error occurs
301
302 Returns:
303 Value that should be assigned to that symbol, or None if it was
304 optional and not found
305
306 Raises:
307 ValueError if the symbol is invalid or not found, or references a
308 property which is not supported
309 """
310 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
311 if not m:
312 raise ValueError("%s: Symbol '%s' has invalid format" %
313 (msg, sym_name))
314 entry_name, prop_name = m.groups()
315 entry_name = entry_name.replace('_', '-')
316 entry = self._entries.get(entry_name)
317 if not entry:
318 if entry_name.endswith('-any'):
319 root = entry_name[:-4]
320 for name in self._entries:
321 if name.startswith(root):
322 rest = name[len(root):]
323 if rest in ['', '-img', '-nodtb']:
324 entry = self._entries[name]
325 if not entry:
326 err = ("%s: Entry '%s' not found in list (%s)" %
327 (msg, entry_name, ','.join(self._entries.keys())))
328 if optional:
329 print('Warning: %s' % err, file=sys.stderr)
330 return None
331 raise ValueError(err)
Simon Glasse8561af2018-08-01 15:22:37 -0600332 if prop_name == 'offset':
333 return entry.offset
Simon Glasseca32212018-06-01 09:38:12 -0600334 else:
335 raise ValueError("%s: No such property '%s'" % (msg, prop_name))
336
337 def GetEntries(self):
338 return self._entries
Simon Glass30732662018-06-01 09:38:20 -0600339
340 def WriteMap(self, fd, indent):
341 """Write a map of the section to a .map file
342
343 Args:
344 fd: File to write the map to
345 """
346 for entry in self._entries.values():
347 entry.WriteMap(fd, indent)