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