blob: 06a6711350a82aaf018e6861032cea9cbbdd9837 [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
93 def CheckSize(self):
94 """Check that the section contents does not exceed its size, etc."""
95 contents_size = 0
96 for entry in self._entries.values():
97 contents_size = max(contents_size, entry.pos + entry.size)
98
99 contents_size -= self._skip_at_start
100
101 size = self._size
102 if not size:
103 size = self._pad_before + contents_size + self._pad_after
104 size = tools.Align(size, self._align_size)
105
106 if self._size and contents_size > self._size:
107 self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
108 (contents_size, contents_size, self._size, self._size))
109 if not self._size:
110 self._size = size
111 if self._size != tools.Align(self._size, self._align_size):
112 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
113 (self._size, self._size, self._align_size, self._align_size))
114 return size
115
116 def _Raise(self, msg):
117 """Raises an error for this section
118
119 Args:
120 msg: Error message to use in the raise string
121 Raises:
122 ValueError()
123 """
124 raise ValueError("Section '%s': %s" % (self._node.path, msg))
125
126 def GetPath(self):
127 """Get the path of an image (in the FDT)
128
129 Returns:
130 Full path of the node for this image
131 """
132 return self._node.path
133
134 def FindEntryType(self, etype):
135 """Find an entry type in the section
136
137 Args:
138 etype: Entry type to find
139 Returns:
140 entry matching that type, or None if not found
141 """
142 for entry in self._entries.values():
143 if entry.etype == etype:
144 return entry
145 return None
146
147 def GetEntryContents(self):
148 """Call ObtainContents() for each entry
149
150 This calls each entry's ObtainContents() a few times until they all
151 return True. We stop calling an entry's function once it returns
152 True. This allows the contents of one entry to depend on another.
153
154 After 3 rounds we give up since it's likely an error.
155 """
156 todo = self._entries.values()
157 for passnum in range(3):
158 next_todo = []
159 for entry in todo:
160 if not entry.ObtainContents():
161 next_todo.append(entry)
162 todo = next_todo
163 if not todo:
164 break
Simon Glass736bb0a2018-07-06 10:27:17 -0600165 if todo:
166 self._Raise('Internal error: Could not complete processing of '
167 'contents: remaining %s' % todo)
168 return True
Simon Glass8f1da502018-06-01 09:38:12 -0600169
170 def _SetEntryPosSize(self, name, pos, size):
171 """Set the position and size of an entry
172
173 Args:
174 name: Entry name to update
175 pos: New position
176 size: New size
177 """
178 entry = self._entries.get(name)
179 if not entry:
180 self._Raise("Unable to set pos/size for unknown entry '%s'" % name)
181 entry.SetPositionSize(self._skip_at_start + pos, size)
182
183 def GetEntryPositions(self):
184 """Handle entries that want to set the position/size of other entries
185
186 This calls each entry's GetPositions() method. If it returns a list
187 of entries to update, it updates them.
188 """
189 for entry in self._entries.values():
190 pos_dict = entry.GetPositions()
191 for name, info in pos_dict.iteritems():
192 self._SetEntryPosSize(name, *info)
193
194 def PackEntries(self):
195 """Pack all entries into the section"""
196 pos = self._skip_at_start
197 for entry in self._entries.values():
198 pos = entry.Pack(pos)
199
200 def _SortEntries(self):
201 """Sort entries by position"""
202 entries = sorted(self._entries.values(), key=lambda entry: entry.pos)
203 self._entries.clear()
204 for entry in entries:
205 self._entries[entry._node.name] = entry
206
207 def CheckEntries(self):
208 """Check that entries do not overlap or extend outside the section"""
209 if self._sort:
210 self._SortEntries()
211 pos = 0
212 prev_name = 'None'
213 for entry in self._entries.values():
Simon Glass18546952018-06-01 09:38:16 -0600214 entry.CheckPosition()
Simon Glass8f1da502018-06-01 09:38:12 -0600215 if (entry.pos < self._skip_at_start or
216 entry.pos >= self._skip_at_start + self._size):
217 entry.Raise("Position %#x (%d) is outside the section starting "
218 "at %#x (%d)" %
219 (entry.pos, entry.pos, self._skip_at_start,
220 self._skip_at_start))
221 if entry.pos < pos:
222 entry.Raise("Position %#x (%d) overlaps with previous entry '%s' "
223 "ending at %#x (%d)" %
224 (entry.pos, entry.pos, prev_name, pos, pos))
225 pos = entry.pos + entry.size
226 prev_name = entry.GetPath()
227
228 def ProcessEntryContents(self):
229 """Call the ProcessContents() method for each entry
230
231 This is intended to adjust the contents as needed by the entry type.
232 """
233 for entry in self._entries.values():
234 entry.ProcessContents()
235
236 def WriteSymbols(self):
237 """Write symbol values into binary files for access at run time"""
238 for entry in self._entries.values():
239 entry.WriteSymbols(self)
240
241 def BuildSection(self, fd, base_pos):
242 """Write the section to a file"""
243 fd.seek(base_pos)
244 fd.write(self.GetData())
245
246 def GetData(self):
247 """Write the section to a file"""
248 section_data = chr(self._pad_byte) * self._size
249
250 for entry in self._entries.values():
251 data = entry.GetData()
252 base = self._pad_before + entry.pos - self._skip_at_start
253 section_data = (section_data[:base] + data +
254 section_data[base + len(data):])
255 return section_data
256
257 def LookupSymbol(self, sym_name, optional, msg):
258 """Look up a symbol in an ELF file
259
260 Looks up a symbol in an ELF file. Only entry types which come from an
261 ELF image can be used by this function.
262
263 At present the only entry property supported is pos.
264
265 Args:
266 sym_name: Symbol name in the ELF file to look up in the format
267 _binman_<entry>_prop_<property> where <entry> is the name of
268 the entry and <property> is the property to find (e.g.
269 _binman_u_boot_prop_pos). As a special case, you can append
270 _any to <entry> to have it search for any matching entry. E.g.
271 _binman_u_boot_any_prop_pos will match entries called u-boot,
272 u-boot-img and u-boot-nodtb)
273 optional: True if the symbol is optional. If False this function
274 will raise if the symbol is not found
275 msg: Message to display if an error occurs
276
277 Returns:
278 Value that should be assigned to that symbol, or None if it was
279 optional and not found
280
281 Raises:
282 ValueError if the symbol is invalid or not found, or references a
283 property which is not supported
284 """
285 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
286 if not m:
287 raise ValueError("%s: Symbol '%s' has invalid format" %
288 (msg, sym_name))
289 entry_name, prop_name = m.groups()
290 entry_name = entry_name.replace('_', '-')
291 entry = self._entries.get(entry_name)
292 if not entry:
293 if entry_name.endswith('-any'):
294 root = entry_name[:-4]
295 for name in self._entries:
296 if name.startswith(root):
297 rest = name[len(root):]
298 if rest in ['', '-img', '-nodtb']:
299 entry = self._entries[name]
300 if not entry:
301 err = ("%s: Entry '%s' not found in list (%s)" %
302 (msg, entry_name, ','.join(self._entries.keys())))
303 if optional:
304 print('Warning: %s' % err, file=sys.stderr)
305 return None
306 raise ValueError(err)
307 if prop_name == 'pos':
308 return entry.pos
309 else:
310 raise ValueError("%s: No such property '%s'" % (msg, prop_name))
311
312 def GetEntries(self):
313 return self._entries
Simon Glass3b0c3822018-06-01 09:38:20 -0600314
315 def WriteMap(self, fd, indent):
316 """Write a map of the section to a .map file
317
318 Args:
319 fd: File to write the map to
320 """
321 for entry in self._entries.values():
322 entry.WriteMap(fd, indent)