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