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