1# SPDX-License-Identifier: GPL-2.0+ 2# Copyright (c) 2016 Google, Inc 3# 4# Base class for all entries 5# 6 7from __future__ import print_function 8 9from collections import namedtuple 10 11# importlib was introduced in Python 2.7 but there was a report of it not 12# working in 2.7.12, so we work around this: 13# http://lists.denx.de/pipermail/u-boot/2016-October/269729.html 14try: 15 import importlib 16 have_importlib = True 17except: 18 have_importlib = False 19 20import fdt_util 21import control 22import os 23import sys 24import tools 25 26modules = {} 27 28our_path = os.path.dirname(os.path.realpath(__file__)) 29 30 31# An argument which can be passed to entries on the command line, in lieu of 32# device-tree properties. 33EntryArg = namedtuple('EntryArg', ['name', 'datatype']) 34 35 36class Entry(object): 37 """An Entry in the section 38 39 An entry corresponds to a single node in the device-tree description 40 of the section. Each entry ends up being a part of the final section. 41 Entries can be placed either right next to each other, or with padding 42 between them. The type of the entry determines the data that is in it. 43 44 This class is not used by itself. All entry objects are subclasses of 45 Entry. 46 47 Attributes: 48 section: Section object containing this entry 49 node: The node that created this entry 50 offset: Offset of entry within the section, None if not known yet (in 51 which case it will be calculated by Pack()) 52 size: Entry size in bytes, None if not known 53 contents_size: Size of contents in bytes, 0 by default 54 align: Entry start offset alignment, or None 55 align_size: Entry size alignment, or None 56 align_end: Entry end offset alignment, or None 57 pad_before: Number of pad bytes before the contents, 0 if none 58 pad_after: Number of pad bytes after the contents, 0 if none 59 data: Contents of entry (string of bytes) 60 """ 61 def __init__(self, section, etype, node, read_node=True, name_prefix=''): 62 self.section = section 63 self.etype = etype 64 self._node = node 65 self.name = node and (name_prefix + node.name) or 'none' 66 self.offset = None 67 self.size = None 68 self.data = None 69 self.contents_size = 0 70 self.align = None 71 self.align_size = None 72 self.align_end = None 73 self.pad_before = 0 74 self.pad_after = 0 75 self.offset_unset = False 76 self.image_pos = None 77 if read_node: 78 self.ReadNode() 79 80 @staticmethod 81 def Lookup(section, node_path, etype): 82 """Look up the entry class for a node. 83 84 Args: 85 section: Section object containing this node 86 node_node: Path name of Node object containing information about 87 the entry to create (used for errors) 88 etype: Entry type to use 89 90 Returns: 91 The entry class object if found, else None 92 """ 93 # Convert something like 'u-boot@0' to 'u_boot' since we are only 94 # interested in the type. 95 module_name = etype.replace('-', '_') 96 if '@' in module_name: 97 module_name = module_name.split('@')[0] 98 module = modules.get(module_name) 99 100 # Also allow entry-type modules to be brought in from the etype directory. 101 102 # Import the module if we have not already done so. 103 if not module: 104 old_path = sys.path 105 sys.path.insert(0, os.path.join(our_path, 'etype')) 106 try: 107 if have_importlib: 108 module = importlib.import_module(module_name) 109 else: 110 module = __import__(module_name) 111 except ImportError as e: 112 raise ValueError("Unknown entry type '%s' in node '%s' (expected etype/%s.py, error '%s'" % 113 (etype, node_path, module_name, e)) 114 finally: 115 sys.path = old_path 116 modules[module_name] = module 117 118 # Look up the expected class name 119 return getattr(module, 'Entry_%s' % module_name) 120 121 @staticmethod 122 def Create(section, node, etype=None): 123 """Create a new entry for a node. 124 125 Args: 126 section: Section object containing this node 127 node: Node object containing information about the entry to 128 create 129 etype: Entry type to use, or None to work it out (used for tests) 130 131 Returns: 132 A new Entry object of the correct type (a subclass of Entry) 133 """ 134 if not etype: 135 etype = fdt_util.GetString(node, 'type', node.name) 136 obj = Entry.Lookup(section, node.path, etype) 137 138 # Call its constructor to get the object we want. 139 return obj(section, etype, node) 140 141 def ReadNode(self): 142 """Read entry information from the node 143 144 This reads all the fields we recognise from the node, ready for use. 145 """ 146 if 'pos' in self._node.props: 147 self.Raise("Please use 'offset' instead of 'pos'") 148 self.offset = fdt_util.GetInt(self._node, 'offset') 149 self.size = fdt_util.GetInt(self._node, 'size') 150 self.align = fdt_util.GetInt(self._node, 'align') 151 if tools.NotPowerOfTwo(self.align): 152 raise ValueError("Node '%s': Alignment %s must be a power of two" % 153 (self._node.path, self.align)) 154 self.pad_before = fdt_util.GetInt(self._node, 'pad-before', 0) 155 self.pad_after = fdt_util.GetInt(self._node, 'pad-after', 0) 156 self.align_size = fdt_util.GetInt(self._node, 'align-size') 157 if tools.NotPowerOfTwo(self.align_size): 158 raise ValueError("Node '%s': Alignment size %s must be a power " 159 "of two" % (self._node.path, self.align_size)) 160 self.align_end = fdt_util.GetInt(self._node, 'align-end') 161 self.offset_unset = fdt_util.GetBool(self._node, 'offset-unset') 162 163 def AddMissingProperties(self): 164 """Add new properties to the device tree as needed for this entry""" 165 for prop in ['offset', 'size', 'image-pos']: 166 if not prop in self._node.props: 167 self._node.AddZeroProp(prop) 168 169 def SetCalculatedProperties(self): 170 """Set the value of device-tree properties calculated by binman""" 171 self._node.SetInt('offset', self.offset) 172 self._node.SetInt('size', self.size) 173 self._node.SetInt('image-pos', self.image_pos) 174 175 def ProcessFdt(self, fdt): 176 return True 177 178 def SetPrefix(self, prefix): 179 """Set the name prefix for a node 180 181 Args: 182 prefix: Prefix to set, or '' to not use a prefix 183 """ 184 if prefix: 185 self.name = prefix + self.name 186 187 def SetContents(self, data): 188 """Set the contents of an entry 189 190 This sets both the data and content_size properties 191 192 Args: 193 data: Data to set to the contents (string) 194 """ 195 self.data = data 196 self.contents_size = len(self.data) 197 198 def ProcessContentsUpdate(self, data): 199 """Update the contens of an entry, after the size is fixed 200 201 This checks that the new data is the same size as the old. 202 203 Args: 204 data: Data to set to the contents (string) 205 206 Raises: 207 ValueError if the new data size is not the same as the old 208 """ 209 if len(data) != self.contents_size: 210 self.Raise('Cannot update entry size from %d to %d' % 211 (len(data), self.contents_size)) 212 self.SetContents(data) 213 214 def ObtainContents(self): 215 """Figure out the contents of an entry. 216 217 Returns: 218 True if the contents were found, False if another call is needed 219 after the other entries are processed. 220 """ 221 # No contents by default: subclasses can implement this 222 return True 223 224 def Pack(self, offset): 225 """Figure out how to pack the entry into the section 226 227 Most of the time the entries are not fully specified. There may be 228 an alignment but no size. In that case we take the size from the 229 contents of the entry. 230 231 If an entry has no hard-coded offset, it will be placed at @offset. 232 233 Once this function is complete, both the offset and size of the 234 entry will be know. 235 236 Args: 237 Current section offset pointer 238 239 Returns: 240 New section offset pointer (after this entry) 241 """ 242 if self.offset is None: 243 if self.offset_unset: 244 self.Raise('No offset set with offset-unset: should another ' 245 'entry provide this correct offset?') 246 self.offset = tools.Align(offset, self.align) 247 needed = self.pad_before + self.contents_size + self.pad_after 248 needed = tools.Align(needed, self.align_size) 249 size = self.size 250 if not size: 251 size = needed 252 new_offset = self.offset + size 253 aligned_offset = tools.Align(new_offset, self.align_end) 254 if aligned_offset != new_offset: 255 size = aligned_offset - self.offset 256 new_offset = aligned_offset 257 258 if not self.size: 259 self.size = size 260 261 if self.size < needed: 262 self.Raise("Entry contents size is %#x (%d) but entry size is " 263 "%#x (%d)" % (needed, needed, self.size, self.size)) 264 # Check that the alignment is correct. It could be wrong if the 265 # and offset or size values were provided (i.e. not calculated), but 266 # conflict with the provided alignment values 267 if self.size != tools.Align(self.size, self.align_size): 268 self.Raise("Size %#x (%d) does not match align-size %#x (%d)" % 269 (self.size, self.size, self.align_size, self.align_size)) 270 if self.offset != tools.Align(self.offset, self.align): 271 self.Raise("Offset %#x (%d) does not match align %#x (%d)" % 272 (self.offset, self.offset, self.align, self.align)) 273 274 return new_offset 275 276 def Raise(self, msg): 277 """Convenience function to raise an error referencing a node""" 278 raise ValueError("Node '%s': %s" % (self._node.path, msg)) 279 280 def GetEntryArgsOrProps(self, props, required=False): 281 """Return the values of a set of properties 282 283 Args: 284 props: List of EntryArg objects 285 286 Raises: 287 ValueError if a property is not found 288 """ 289 values = [] 290 missing = [] 291 for prop in props: 292 python_prop = prop.name.replace('-', '_') 293 if hasattr(self, python_prop): 294 value = getattr(self, python_prop) 295 else: 296 value = None 297 if value is None: 298 value = self.GetArg(prop.name, prop.datatype) 299 if value is None and required: 300 missing.append(prop.name) 301 values.append(value) 302 if missing: 303 self.Raise('Missing required properties/entry args: %s' % 304 (', '.join(missing))) 305 return values 306 307 def GetPath(self): 308 """Get the path of a node 309 310 Returns: 311 Full path of the node for this entry 312 """ 313 return self._node.path 314 315 def GetData(self): 316 return self.data 317 318 def GetOffsets(self): 319 return {} 320 321 def SetOffsetSize(self, pos, size): 322 self.offset = pos 323 self.size = size 324 325 def SetImagePos(self, image_pos): 326 """Set the position in the image 327 328 Args: 329 image_pos: Position of this entry in the image 330 """ 331 self.image_pos = image_pos + self.offset 332 333 def ProcessContents(self): 334 pass 335 336 def WriteSymbols(self, section): 337 """Write symbol values into binary files for access at run time 338 339 Args: 340 section: Section containing the entry 341 """ 342 pass 343 344 def CheckOffset(self): 345 """Check that the entry offsets are correct 346 347 This is used for entries which have extra offset requirements (other 348 than having to be fully inside their section). Sub-classes can implement 349 this function and raise if there is a problem. 350 """ 351 pass 352 353 @staticmethod 354 def WriteMapLine(fd, indent, name, offset, size, image_pos): 355 print('%08x %s%08x %08x %s' % (image_pos, ' ' * indent, offset, 356 size, name), file=fd) 357 358 def WriteMap(self, fd, indent): 359 """Write a map of the entry to a .map file 360 361 Args: 362 fd: File to write the map to 363 indent: Curent indent level of map (0=none, 1=one level, etc.) 364 """ 365 self.WriteMapLine(fd, indent, self.name, self.offset, self.size, 366 self.image_pos) 367 368 def GetEntries(self): 369 """Return a list of entries contained by this entry 370 371 Returns: 372 List of entries, or None if none. A normal entry has no entries 373 within it so will return None 374 """ 375 return None 376 377 def GetArg(self, name, datatype=str): 378 """Get the value of an entry argument or device-tree-node property 379 380 Some node properties can be provided as arguments to binman. First check 381 the entry arguments, and fall back to the device tree if not found 382 383 Args: 384 name: Argument name 385 datatype: Data type (str or int) 386 387 Returns: 388 Value of argument as a string or int, or None if no value 389 390 Raises: 391 ValueError if the argument cannot be converted to in 392 """ 393 value = control.GetEntryArg(name) 394 if value is not None: 395 if datatype == int: 396 try: 397 value = int(value) 398 except ValueError: 399 self.Raise("Cannot convert entry arg '%s' (value '%s') to integer" % 400 (name, value)) 401 elif datatype == str: 402 pass 403 else: 404 raise ValueError("GetArg() internal error: Unknown data type '%s'" % 405 datatype) 406 else: 407 value = fdt_util.GetDatatype(self._node, name, datatype) 408 return value 409 410 @staticmethod 411 def WriteDocs(modules, test_missing=None): 412 """Write out documentation about the various entry types to stdout 413 414 Args: 415 modules: List of modules to include 416 test_missing: Used for testing. This is a module to report 417 as missing 418 """ 419 print('''Binman Entry Documentation 420=========================== 421 422This file describes the entry types supported by binman. These entry types can 423be placed in an image one by one to build up a final firmware image. It is 424fairly easy to create new entry types. Just add a new file to the 'etype' 425directory. You can use the existing entries as examples. 426 427Note that some entries are subclasses of others, using and extending their 428features to produce new behaviours. 429 430 431''') 432 modules = sorted(modules) 433 434 # Don't show the test entry 435 if '_testing' in modules: 436 modules.remove('_testing') 437 missing = [] 438 for name in modules: 439 module = Entry.Lookup(name, name, name) 440 docs = getattr(module, '__doc__') 441 if test_missing == name: 442 docs = None 443 if docs: 444 lines = docs.splitlines() 445 first_line = lines[0] 446 rest = [line[4:] for line in lines[1:]] 447 hdr = 'Entry: %s: %s' % (name.replace('_', '-'), first_line) 448 print(hdr) 449 print('-' * len(hdr)) 450 print('\n'.join(rest)) 451 print() 452 print() 453 else: 454 missing.append(name) 455 456 if missing: 457 raise ValueError('Documentation is missing for modules: %s' % 458 ', '.join(missing)) 459