blob: de439ef625fcb894f547e034be8510545aa60d40 [file] [log] [blame]
Simon Glass8f1da502018-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
28 effectively changes the place where entry position 0 starts
29 _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
32 _sort: True if entries should be sorted by position, False if they
33 must be in-order in the device tree description
34 _skip_at_start: Number of bytes before the first entry starts. These
35 effectively adjust the starting position of entries. For example,
36 if _pad_before is 16, then the first entry would start at 16.
37 An entry with pos = 20 would in fact be written at position 4
38 in the image file.
39 _end_4gb: Indicates that the section ends at the 4GB boundary. This is
40 used for x86 images, which want to use positions such that a
41 memory address (like 0xff800000) is the first entry position.
42 This causes _skip_at_start to be set to the starting memory
43 address.
Simon Glassc8d48ef2018-06-01 09:38:21 -060044 _name_prefix: Prefix to add to the name of all entries within this
45 section
Simon Glass8f1da502018-06-01 09:38:12 -060046 _entries: OrderedDict() of entries
47 """
48 def __init__(self, name, node, test=False):
49 global entry
50 global Entry
51 import entry
52 from entry import Entry
53
54 self._node = node
55 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 Glassc8d48ef2018-06-01 09:38:21 -060063 self._name_prefix = ''
Simon Glass8f1da502018-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)
79 self._sort = fdt_util.GetBool(self._node, 'sort-by-pos')
80 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 Glassc8d48ef2018-06-01 09:38:21 -060085 self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
Simon Glass8f1da502018-06-01 09:38:12 -060086
87 def _ReadEntries(self):
88 for node in self._node.subnodes:
Simon Glassc8d48ef2018-06-01 09:38:21 -060089 entry = Entry.Create(self, node)
90 entry.SetPrefix(self._name_prefix)
91 self._entries[node.name] = entry
Simon Glass8f1da502018-06-01 09:38:12 -060092
Simon Glass078ab1a2018-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 Glassecab8972018-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 Glass8f1da502018-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():
120 contents_size = max(contents_size, entry.pos + entry.size)
121
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 Glass736bb0a2018-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 Glass8f1da502018-06-01 09:38:12 -0600192
193 def _SetEntryPosSize(self, name, pos, size):
194 """Set the position and size of an entry
195
196 Args:
197 name: Entry name to update
198 pos: New position
199 size: New size
200 """
201 entry = self._entries.get(name)
202 if not entry:
203 self._Raise("Unable to set pos/size for unknown entry '%s'" % name)
204 entry.SetPositionSize(self._skip_at_start + pos, size)
205
206 def GetEntryPositions(self):
207 """Handle entries that want to set the position/size of other entries
208
209 This calls each entry's GetPositions() method. If it returns a list
210 of entries to update, it updates them.
211 """
212 for entry in self._entries.values():
213 pos_dict = entry.GetPositions()
214 for name, info in pos_dict.iteritems():
215 self._SetEntryPosSize(name, *info)
216
217 def PackEntries(self):
218 """Pack all entries into the section"""
219 pos = self._skip_at_start
220 for entry in self._entries.values():
221 pos = entry.Pack(pos)
222
223 def _SortEntries(self):
224 """Sort entries by position"""
225 entries = sorted(self._entries.values(), key=lambda entry: entry.pos)
226 self._entries.clear()
227 for entry in entries:
228 self._entries[entry._node.name] = entry
229
230 def CheckEntries(self):
231 """Check that entries do not overlap or extend outside the section"""
232 if self._sort:
233 self._SortEntries()
234 pos = 0
235 prev_name = 'None'
236 for entry in self._entries.values():
Simon Glass18546952018-06-01 09:38:16 -0600237 entry.CheckPosition()
Simon Glass8f1da502018-06-01 09:38:12 -0600238 if (entry.pos < self._skip_at_start or
239 entry.pos >= self._skip_at_start + self._size):
240 entry.Raise("Position %#x (%d) is outside the section starting "
241 "at %#x (%d)" %
242 (entry.pos, entry.pos, self._skip_at_start,
243 self._skip_at_start))
244 if entry.pos < pos:
245 entry.Raise("Position %#x (%d) overlaps with previous entry '%s' "
246 "ending at %#x (%d)" %
247 (entry.pos, entry.pos, prev_name, pos, pos))
248 pos = entry.pos + entry.size
249 prev_name = entry.GetPath()
250
251 def ProcessEntryContents(self):
252 """Call the ProcessContents() method for each entry
253
254 This is intended to adjust the contents as needed by the entry type.
255 """
256 for entry in self._entries.values():
257 entry.ProcessContents()
258
259 def WriteSymbols(self):
260 """Write symbol values into binary files for access at run time"""
261 for entry in self._entries.values():
262 entry.WriteSymbols(self)
263
264 def BuildSection(self, fd, base_pos):
265 """Write the section to a file"""
266 fd.seek(base_pos)
267 fd.write(self.GetData())
268
269 def GetData(self):
270 """Write the section to a file"""
271 section_data = chr(self._pad_byte) * self._size
272
273 for entry in self._entries.values():
274 data = entry.GetData()
275 base = self._pad_before + entry.pos - self._skip_at_start
276 section_data = (section_data[:base] + data +
277 section_data[base + len(data):])
278 return section_data
279
280 def LookupSymbol(self, sym_name, optional, msg):
281 """Look up a symbol in an ELF file
282
283 Looks up a symbol in an ELF file. Only entry types which come from an
284 ELF image can be used by this function.
285
286 At present the only entry property supported is pos.
287
288 Args:
289 sym_name: Symbol name in the ELF file to look up in the format
290 _binman_<entry>_prop_<property> where <entry> is the name of
291 the entry and <property> is the property to find (e.g.
292 _binman_u_boot_prop_pos). As a special case, you can append
293 _any to <entry> to have it search for any matching entry. E.g.
294 _binman_u_boot_any_prop_pos will match entries called u-boot,
295 u-boot-img and u-boot-nodtb)
296 optional: True if the symbol is optional. If False this function
297 will raise if the symbol is not found
298 msg: Message to display if an error occurs
299
300 Returns:
301 Value that should be assigned to that symbol, or None if it was
302 optional and not found
303
304 Raises:
305 ValueError if the symbol is invalid or not found, or references a
306 property which is not supported
307 """
308 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
309 if not m:
310 raise ValueError("%s: Symbol '%s' has invalid format" %
311 (msg, sym_name))
312 entry_name, prop_name = m.groups()
313 entry_name = entry_name.replace('_', '-')
314 entry = self._entries.get(entry_name)
315 if not entry:
316 if entry_name.endswith('-any'):
317 root = entry_name[:-4]
318 for name in self._entries:
319 if name.startswith(root):
320 rest = name[len(root):]
321 if rest in ['', '-img', '-nodtb']:
322 entry = self._entries[name]
323 if not entry:
324 err = ("%s: Entry '%s' not found in list (%s)" %
325 (msg, entry_name, ','.join(self._entries.keys())))
326 if optional:
327 print('Warning: %s' % err, file=sys.stderr)
328 return None
329 raise ValueError(err)
330 if prop_name == 'pos':
331 return entry.pos
332 else:
333 raise ValueError("%s: No such property '%s'" % (msg, prop_name))
334
335 def GetEntries(self):
336 return self._entries
Simon Glass3b0c3822018-06-01 09:38:20 -0600337
338 def WriteMap(self, fd, indent):
339 """Write a map of the section to a .map file
340
341 Args:
342 fd: File to write the map to
343 """
344 for entry in self._entries.values():
345 entry.WriteMap(fd, indent)