blob: 331f806d9ab4f32838e36b4817bf63fc9181fb50 [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
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.
44 _entries: OrderedDict() of entries
45 """
46 def __init__(self, name, node, test=False):
47 global entry
48 global Entry
49 import entry
50 from entry import Entry
51
52 self._node = node
53 self._size = None
54 self._align_size = None
55 self._pad_before = 0
56 self._pad_after = 0
57 self._pad_byte = 0
58 self._sort = False
59 self._skip_at_start = 0
60 self._end_4gb = False
61 self._entries = OrderedDict()
62 if not test:
63 self._ReadNode()
64 self._ReadEntries()
65
66 def _ReadNode(self):
67 """Read properties from the section node"""
68 self._size = fdt_util.GetInt(self._node, 'size')
69 self._align_size = fdt_util.GetInt(self._node, 'align-size')
70 if tools.NotPowerOfTwo(self._align_size):
71 self._Raise("Alignment size %s must be a power of two" %
72 self._align_size)
73 self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
74 self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
75 self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
76 self._sort = fdt_util.GetBool(self._node, 'sort-by-pos')
77 self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
78 if self._end_4gb and not self._size:
79 self._Raise("Section size must be provided when using end-at-4gb")
80 if self._end_4gb:
81 self._skip_at_start = 0x100000000 - self._size
82
83 def _ReadEntries(self):
84 for node in self._node.subnodes:
85 self._entries[node.name] = Entry.Create(self, node)
86
87 def CheckSize(self):
88 """Check that the section contents does not exceed its size, etc."""
89 contents_size = 0
90 for entry in self._entries.values():
91 contents_size = max(contents_size, entry.pos + entry.size)
92
93 contents_size -= self._skip_at_start
94
95 size = self._size
96 if not size:
97 size = self._pad_before + contents_size + self._pad_after
98 size = tools.Align(size, self._align_size)
99
100 if self._size and contents_size > self._size:
101 self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" %
102 (contents_size, contents_size, self._size, self._size))
103 if not self._size:
104 self._size = size
105 if self._size != tools.Align(self._size, self._align_size):
106 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
107 (self._size, self._size, self._align_size, self._align_size))
108 return size
109
110 def _Raise(self, msg):
111 """Raises an error for this section
112
113 Args:
114 msg: Error message to use in the raise string
115 Raises:
116 ValueError()
117 """
118 raise ValueError("Section '%s': %s" % (self._node.path, msg))
119
120 def GetPath(self):
121 """Get the path of an image (in the FDT)
122
123 Returns:
124 Full path of the node for this image
125 """
126 return self._node.path
127
128 def FindEntryType(self, etype):
129 """Find an entry type in the section
130
131 Args:
132 etype: Entry type to find
133 Returns:
134 entry matching that type, or None if not found
135 """
136 for entry in self._entries.values():
137 if entry.etype == etype:
138 return entry
139 return None
140
141 def GetEntryContents(self):
142 """Call ObtainContents() for each entry
143
144 This calls each entry's ObtainContents() a few times until they all
145 return True. We stop calling an entry's function once it returns
146 True. This allows the contents of one entry to depend on another.
147
148 After 3 rounds we give up since it's likely an error.
149 """
150 todo = self._entries.values()
151 for passnum in range(3):
152 next_todo = []
153 for entry in todo:
154 if not entry.ObtainContents():
155 next_todo.append(entry)
156 todo = next_todo
157 if not todo:
158 break
159
160 def _SetEntryPosSize(self, name, pos, size):
161 """Set the position and size of an entry
162
163 Args:
164 name: Entry name to update
165 pos: New position
166 size: New size
167 """
168 entry = self._entries.get(name)
169 if not entry:
170 self._Raise("Unable to set pos/size for unknown entry '%s'" % name)
171 entry.SetPositionSize(self._skip_at_start + pos, size)
172
173 def GetEntryPositions(self):
174 """Handle entries that want to set the position/size of other entries
175
176 This calls each entry's GetPositions() method. If it returns a list
177 of entries to update, it updates them.
178 """
179 for entry in self._entries.values():
180 pos_dict = entry.GetPositions()
181 for name, info in pos_dict.iteritems():
182 self._SetEntryPosSize(name, *info)
183
184 def PackEntries(self):
185 """Pack all entries into the section"""
186 pos = self._skip_at_start
187 for entry in self._entries.values():
188 pos = entry.Pack(pos)
189
190 def _SortEntries(self):
191 """Sort entries by position"""
192 entries = sorted(self._entries.values(), key=lambda entry: entry.pos)
193 self._entries.clear()
194 for entry in entries:
195 self._entries[entry._node.name] = entry
196
197 def CheckEntries(self):
198 """Check that entries do not overlap or extend outside the section"""
199 if self._sort:
200 self._SortEntries()
201 pos = 0
202 prev_name = 'None'
203 for entry in self._entries.values():
Simon Glassa91e1152018-06-01 09:38:16 -0600204 entry.CheckPosition()
Simon Glasseca32212018-06-01 09:38:12 -0600205 if (entry.pos < self._skip_at_start or
206 entry.pos >= self._skip_at_start + self._size):
207 entry.Raise("Position %#x (%d) is outside the section starting "
208 "at %#x (%d)" %
209 (entry.pos, entry.pos, self._skip_at_start,
210 self._skip_at_start))
211 if entry.pos < pos:
212 entry.Raise("Position %#x (%d) overlaps with previous entry '%s' "
213 "ending at %#x (%d)" %
214 (entry.pos, entry.pos, prev_name, pos, pos))
215 pos = entry.pos + entry.size
216 prev_name = entry.GetPath()
217
218 def ProcessEntryContents(self):
219 """Call the ProcessContents() method for each entry
220
221 This is intended to adjust the contents as needed by the entry type.
222 """
223 for entry in self._entries.values():
224 entry.ProcessContents()
225
226 def WriteSymbols(self):
227 """Write symbol values into binary files for access at run time"""
228 for entry in self._entries.values():
229 entry.WriteSymbols(self)
230
231 def BuildSection(self, fd, base_pos):
232 """Write the section to a file"""
233 fd.seek(base_pos)
234 fd.write(self.GetData())
235
236 def GetData(self):
237 """Write the section to a file"""
238 section_data = chr(self._pad_byte) * self._size
239
240 for entry in self._entries.values():
241 data = entry.GetData()
242 base = self._pad_before + entry.pos - self._skip_at_start
243 section_data = (section_data[:base] + data +
244 section_data[base + len(data):])
245 return section_data
246
247 def LookupSymbol(self, sym_name, optional, msg):
248 """Look up a symbol in an ELF file
249
250 Looks up a symbol in an ELF file. Only entry types which come from an
251 ELF image can be used by this function.
252
253 At present the only entry property supported is pos.
254
255 Args:
256 sym_name: Symbol name in the ELF file to look up in the format
257 _binman_<entry>_prop_<property> where <entry> is the name of
258 the entry and <property> is the property to find (e.g.
259 _binman_u_boot_prop_pos). As a special case, you can append
260 _any to <entry> to have it search for any matching entry. E.g.
261 _binman_u_boot_any_prop_pos will match entries called u-boot,
262 u-boot-img and u-boot-nodtb)
263 optional: True if the symbol is optional. If False this function
264 will raise if the symbol is not found
265 msg: Message to display if an error occurs
266
267 Returns:
268 Value that should be assigned to that symbol, or None if it was
269 optional and not found
270
271 Raises:
272 ValueError if the symbol is invalid or not found, or references a
273 property which is not supported
274 """
275 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
276 if not m:
277 raise ValueError("%s: Symbol '%s' has invalid format" %
278 (msg, sym_name))
279 entry_name, prop_name = m.groups()
280 entry_name = entry_name.replace('_', '-')
281 entry = self._entries.get(entry_name)
282 if not entry:
283 if entry_name.endswith('-any'):
284 root = entry_name[:-4]
285 for name in self._entries:
286 if name.startswith(root):
287 rest = name[len(root):]
288 if rest in ['', '-img', '-nodtb']:
289 entry = self._entries[name]
290 if not entry:
291 err = ("%s: Entry '%s' not found in list (%s)" %
292 (msg, entry_name, ','.join(self._entries.keys())))
293 if optional:
294 print('Warning: %s' % err, file=sys.stderr)
295 return None
296 raise ValueError(err)
297 if prop_name == 'pos':
298 return entry.pos
299 else:
300 raise ValueError("%s: No such property '%s'" % (msg, prop_name))
301
302 def GetEntries(self):
303 return self._entries