1# 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 offset 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 offset, 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 offset of entries. For example, 36 if _pad_before is 16, then the first entry would start at 16. 37 An entry with offset = 20 would in fact be written at offset 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 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. 43 _name_prefix: Prefix to add to the name of all entries within this 44 section 45 _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._name = name 54 self._node = node 55 self._offset = 0 56 self._size = None 57 self._align_size = None 58 self._pad_before = 0 59 self._pad_after = 0 60 self._pad_byte = 0 61 self._sort = False 62 self._skip_at_start = 0 63 self._end_4gb = False 64 self._name_prefix = '' 65 self._entries = OrderedDict() 66 if not test: 67 self._ReadNode() 68 self._ReadEntries() 69 70 def _ReadNode(self): 71 """Read properties from the section node""" 72 self._size = fdt_util.GetInt(self._node, 'size') 73 self._align_size = fdt_util.GetInt(self._node, 'align-size') 74 if tools.NotPowerOfTwo(self._align_size): 75 self._Raise("Alignment size %s must be a power of two" % 76 self._align_size) 77 self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0) 78 self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0) 79 self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0) 80 self._sort = fdt_util.GetBool(self._node, 'sort-by-offset') 81 self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb') 82 if self._end_4gb and not self._size: 83 self._Raise("Section size must be provided when using end-at-4gb") 84 if self._end_4gb: 85 self._skip_at_start = 0x100000000 - self._size 86 self._name_prefix = fdt_util.GetString(self._node, 'name-prefix') 87 88 def _ReadEntries(self): 89 for node in self._node.subnodes: 90 entry = Entry.Create(self, node) 91 entry.SetPrefix(self._name_prefix) 92 self._entries[node.name] = entry 93 94 def SetOffset(self, offset): 95 self._offset = offset 96 97 def AddMissingProperties(self): 98 """Add new properties to the device tree as needed for this entry""" 99 for prop in ['offset', 'size', 'image-pos']: 100 if not prop in self._node.props: 101 self._node.AddZeroProp(prop) 102 for entry in self._entries.values(): 103 entry.AddMissingProperties() 104 105 def SetCalculatedProperties(self): 106 self._node.SetInt('offset', self._offset) 107 self._node.SetInt('size', self._size) 108 self._node.SetInt('image-pos', self._image_pos) 109 for entry in self._entries.values(): 110 entry.SetCalculatedProperties() 111 112 def ProcessFdt(self, fdt): 113 todo = self._entries.values() 114 for passnum in range(3): 115 next_todo = [] 116 for entry in todo: 117 if not entry.ProcessFdt(fdt): 118 next_todo.append(entry) 119 todo = next_todo 120 if not todo: 121 break 122 if todo: 123 self._Raise('Internal error: Could not complete processing of Fdt: ' 124 'remaining %s' % todo) 125 return True 126 127 def CheckSize(self): 128 """Check that the section contents does not exceed its size, etc.""" 129 contents_size = 0 130 for entry in self._entries.values(): 131 contents_size = max(contents_size, entry.offset + entry.size) 132 133 contents_size -= self._skip_at_start 134 135 size = self._size 136 if not size: 137 size = self._pad_before + contents_size + self._pad_after 138 size = tools.Align(size, self._align_size) 139 140 if self._size and contents_size > self._size: 141 self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" % 142 (contents_size, contents_size, self._size, self._size)) 143 if not self._size: 144 self._size = size 145 if self._size != tools.Align(self._size, self._align_size): 146 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" % 147 (self._size, self._size, self._align_size, self._align_size)) 148 return size 149 150 def _Raise(self, msg): 151 """Raises an error for this section 152 153 Args: 154 msg: Error message to use in the raise string 155 Raises: 156 ValueError() 157 """ 158 raise ValueError("Section '%s': %s" % (self._node.path, msg)) 159 160 def GetPath(self): 161 """Get the path of an image (in the FDT) 162 163 Returns: 164 Full path of the node for this image 165 """ 166 return self._node.path 167 168 def FindEntryType(self, etype): 169 """Find an entry type in the section 170 171 Args: 172 etype: Entry type to find 173 Returns: 174 entry matching that type, or None if not found 175 """ 176 for entry in self._entries.values(): 177 if entry.etype == etype: 178 return entry 179 return None 180 181 def GetEntryContents(self): 182 """Call ObtainContents() for each entry 183 184 This calls each entry's ObtainContents() a few times until they all 185 return True. We stop calling an entry's function once it returns 186 True. This allows the contents of one entry to depend on another. 187 188 After 3 rounds we give up since it's likely an error. 189 """ 190 todo = self._entries.values() 191 for passnum in range(3): 192 next_todo = [] 193 for entry in todo: 194 if not entry.ObtainContents(): 195 next_todo.append(entry) 196 todo = next_todo 197 if not todo: 198 break 199 if todo: 200 self._Raise('Internal error: Could not complete processing of ' 201 'contents: remaining %s' % todo) 202 return True 203 204 def _SetEntryOffsetSize(self, name, offset, size): 205 """Set the offset and size of an entry 206 207 Args: 208 name: Entry name to update 209 offset: New offset 210 size: New size 211 """ 212 entry = self._entries.get(name) 213 if not entry: 214 self._Raise("Unable to set offset/size for unknown entry '%s'" % 215 name) 216 entry.SetOffsetSize(self._skip_at_start + offset, size) 217 218 def GetEntryOffsets(self): 219 """Handle entries that want to set the offset/size of other entries 220 221 This calls each entry's GetOffsets() method. If it returns a list 222 of entries to update, it updates them. 223 """ 224 for entry in self._entries.values(): 225 offset_dict = entry.GetOffsets() 226 for name, info in offset_dict.iteritems(): 227 self._SetEntryOffsetSize(name, *info) 228 229 def PackEntries(self): 230 """Pack all entries into the section""" 231 offset = self._skip_at_start 232 for entry in self._entries.values(): 233 offset = entry.Pack(offset) 234 self._size = self.CheckSize() 235 236 def _SortEntries(self): 237 """Sort entries by offset""" 238 entries = sorted(self._entries.values(), key=lambda entry: entry.offset) 239 self._entries.clear() 240 for entry in entries: 241 self._entries[entry._node.name] = entry 242 243 def CheckEntries(self): 244 """Check that entries do not overlap or extend outside the section""" 245 if self._sort: 246 self._SortEntries() 247 offset = 0 248 prev_name = 'None' 249 for entry in self._entries.values(): 250 entry.CheckOffset() 251 if (entry.offset < self._skip_at_start or 252 entry.offset >= self._skip_at_start + self._size): 253 entry.Raise("Offset %#x (%d) is outside the section starting " 254 "at %#x (%d)" % 255 (entry.offset, entry.offset, self._skip_at_start, 256 self._skip_at_start)) 257 if entry.offset < offset: 258 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' " 259 "ending at %#x (%d)" % 260 (entry.offset, entry.offset, prev_name, offset, offset)) 261 offset = entry.offset + entry.size 262 prev_name = entry.GetPath() 263 264 def SetImagePos(self, image_pos): 265 self._image_pos = image_pos 266 for entry in self._entries.values(): 267 entry.SetImagePos(image_pos) 268 269 def ProcessEntryContents(self): 270 """Call the ProcessContents() method for each entry 271 272 This is intended to adjust the contents as needed by the entry type. 273 """ 274 for entry in self._entries.values(): 275 entry.ProcessContents() 276 277 def WriteSymbols(self): 278 """Write symbol values into binary files for access at run time""" 279 for entry in self._entries.values(): 280 entry.WriteSymbols(self) 281 282 def BuildSection(self, fd, base_offset): 283 """Write the section to a file""" 284 fd.seek(base_offset) 285 fd.write(self.GetData()) 286 287 def GetData(self): 288 """Get the contents of the section""" 289 section_data = chr(self._pad_byte) * self._size 290 291 for entry in self._entries.values(): 292 data = entry.GetData() 293 base = self._pad_before + entry.offset - self._skip_at_start 294 section_data = (section_data[:base] + data + 295 section_data[base + len(data):]) 296 return section_data 297 298 def LookupSymbol(self, sym_name, optional, msg): 299 """Look up a symbol in an ELF file 300 301 Looks up a symbol in an ELF file. Only entry types which come from an 302 ELF image can be used by this function. 303 304 At present the only entry property supported is offset. 305 306 Args: 307 sym_name: Symbol name in the ELF file to look up in the format 308 _binman_<entry>_prop_<property> where <entry> is the name of 309 the entry and <property> is the property to find (e.g. 310 _binman_u_boot_prop_offset). As a special case, you can append 311 _any to <entry> to have it search for any matching entry. E.g. 312 _binman_u_boot_any_prop_offset will match entries called u-boot, 313 u-boot-img and u-boot-nodtb) 314 optional: True if the symbol is optional. If False this function 315 will raise if the symbol is not found 316 msg: Message to display if an error occurs 317 318 Returns: 319 Value that should be assigned to that symbol, or None if it was 320 optional and not found 321 322 Raises: 323 ValueError if the symbol is invalid or not found, or references a 324 property which is not supported 325 """ 326 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name) 327 if not m: 328 raise ValueError("%s: Symbol '%s' has invalid format" % 329 (msg, sym_name)) 330 entry_name, prop_name = m.groups() 331 entry_name = entry_name.replace('_', '-') 332 entry = self._entries.get(entry_name) 333 if not entry: 334 if entry_name.endswith('-any'): 335 root = entry_name[:-4] 336 for name in self._entries: 337 if name.startswith(root): 338 rest = name[len(root):] 339 if rest in ['', '-img', '-nodtb']: 340 entry = self._entries[name] 341 if not entry: 342 err = ("%s: Entry '%s' not found in list (%s)" % 343 (msg, entry_name, ','.join(self._entries.keys()))) 344 if optional: 345 print('Warning: %s' % err, file=sys.stderr) 346 return None 347 raise ValueError(err) 348 if prop_name == 'offset': 349 return entry.offset 350 elif prop_name == 'image_pos': 351 return entry.image_pos 352 else: 353 raise ValueError("%s: No such property '%s'" % (msg, prop_name)) 354 355 def GetEntries(self): 356 """Get the number of entries in a section 357 358 Returns: 359 Number of entries in a section 360 """ 361 return self._entries 362 363 def GetSize(self): 364 """Get the size of a section in bytes 365 366 This is only meaningful if the section has a pre-defined size, or the 367 entries within it have been packed, so that the size has been 368 calculated. 369 370 Returns: 371 Entry size in bytes 372 """ 373 return self._size 374 375 def WriteMap(self, fd, indent): 376 """Write a map of the section to a .map file 377 378 Args: 379 fd: File to write the map to 380 """ 381 Entry.WriteMapLine(fd, indent, self._name, self._offset, self._size, 382 self._image_pos) 383 for entry in self._entries.values(): 384 entry.WriteMap(fd, indent + 1) 385 386 def GetContentsByPhandle(self, phandle, source_entry): 387 """Get the data contents of an entry specified by a phandle 388 389 This uses a phandle to look up a node and and find the entry 390 associated with it. Then it returnst he contents of that entry. 391 392 Args: 393 phandle: Phandle to look up (integer) 394 source_entry: Entry containing that phandle (used for error 395 reporting) 396 397 Returns: 398 data from associated entry (as a string), or None if not found 399 """ 400 node = self._node.GetFdt().LookupPhandle(phandle) 401 if not node: 402 source_entry.Raise("Cannot find node for phandle %d" % phandle) 403 for entry in self._entries.values(): 404 if entry._node == node: 405 if entry.data is None: 406 return None 407 return entry.data 408 source_entry.Raise("Cannot find entry for node '%s'" % node.name) 409