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