1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423
|
# SPDX-License-Identifier: GPL-2.0+
# Copyright (c) 2016 Google, Inc
#
# Base class for all entries
#
from collections import namedtuple
import importlib
import os
import pathlib
import sys
import time
from binman import bintool
from binman import elf
from dtoc import fdt_util
from u_boot_pylib import tools
from u_boot_pylib.tools import to_hex, to_hex_size
from u_boot_pylib import tout
modules = {}
# This is imported if needed
state = None
# An argument which can be passed to entries on the command line, in lieu of
# device-tree properties.
EntryArg = namedtuple('EntryArg', ['name', 'datatype'])
# Information about an entry for use when displaying summaries
EntryInfo = namedtuple('EntryInfo', ['indent', 'name', 'etype', 'size',
'image_pos', 'uncomp_size', 'offset',
'entry'])
class Entry(object):
"""An Entry in the section
An entry corresponds to a single node in the device-tree description
of the section. Each entry ends up being a part of the final section.
Entries can be placed either right next to each other, or with padding
between them. The type of the entry determines the data that is in it.
This class is not used by itself. All entry objects are subclasses of
Entry.
Attributes:
section: Section object containing this entry
node: The node that created this entry
offset: Offset of entry within the section, None if not known yet (in
which case it will be calculated by Pack())
size: Entry size in bytes, None if not known
min_size: Minimum entry size in bytes
pre_reset_size: size as it was before ResetForPack(). This allows us to
keep track of the size we started with and detect size changes
uncomp_size: Size of uncompressed data in bytes, if the entry is
compressed, else None
contents_size: Size of contents in bytes, 0 by default
align: Entry start offset alignment relative to the start of the
containing section, or None
align_size: Entry size alignment, or None
align_end: Entry end offset alignment relative to the start of the
containing section, or None
pad_before: Number of pad bytes before the contents when it is placed
in the containing section, 0 if none. The pad bytes become part of
the entry.
pad_after: Number of pad bytes after the contents when it is placed in
the containing section, 0 if none. The pad bytes become part of
the entry.
data: Contents of entry (string of bytes). This does not include
padding created by pad_before or pad_after. If the entry is
compressed, this contains the compressed data.
uncomp_data: Original uncompressed data, if this entry is compressed,
else None
compress: Compression algoithm used (e.g. 'lz4'), 'none' if none
orig_offset: Original offset value read from node
orig_size: Original size value read from node
missing: True if this entry is missing its contents. Note that if it is
optional, this entry will not appear in the list generated by
entry.CheckMissing() since it is considered OK for it to be missing.
allow_missing: Allow children of this entry to be missing (used by
subclasses such as Entry_section)
allow_fake: Allow creating a dummy fake file if the blob file is not
available. This is mainly used for testing.
external: True if this entry contains an external binary blob
bintools: Bintools used by this entry (only populated for Image)
missing_bintools: List of missing bintools for this entry
update_hash: True if this entry's "hash" subnode should be
updated with a hash of the entry contents
comp_bintool: Bintools used for compress and decompress data
fake_fname: Fake filename, if one was created, else None
required_props (dict of str): Properties which must be present. This can
be added to by subclasses
elf_fname (str): Filename of the ELF file, if this entry holds an ELF
file, or is a binary file produced from an ELF file
auto_write_symbols (bool): True to write ELF symbols into this entry's
contents
absent (bool): True if this entry is absent. This can be controlled by
the entry itself, allowing it to vanish in certain circumstances.
An absent entry is removed during processing so that it does not
appear in the map
optional (bool): True if this entry contains an optional external blob
overlap (bool): True if this entry overlaps with others
preserve (bool): True if this entry should be preserved when updating
firmware. This means that it will not be changed by the update.
This is just a signal: enforcement of this is up to the updater.
This flag does not automatically propagate down to child entries.
build_done (bool): Indicates that the entry data has been built and does
not need to be done again. This is only used with 'binman replace',
to stop sections from being rebuilt if their entries have not been
replaced
symbols_base (int): Use this value as the assumed load address of the
target entry, when calculating the symbol value. If None, this is
0 for blobs and the image-start address for ELF files
"""
fake_dir = None
def __init__(self, section, etype, node, name_prefix='',
auto_write_symbols=False):
# Put this here to allow entry-docs and help to work without libfdt
global state
from binman import state
self.section = section
self.etype = etype
self._node = node
self.name = node and (name_prefix + node.name) or 'none'
self.offset = None
self.size = None
self.min_size = 0
self.pre_reset_size = None
self.uncomp_size = None
self.data = None
self.uncomp_data = None
self.contents_size = 0
self.align = None
self.align_size = None
self.align_end = None
self.pad_before = 0
self.pad_after = 0
self.offset_unset = False
self.image_pos = None
self.extend_size = False
self.compress = 'none'
self.missing = False
self.faked = False
self.external = False
self.allow_missing = False
self.allow_fake = False
self.bintools = {}
self.missing_bintools = []
self.update_hash = True
self.fake_fname = None
self.required_props = []
self.comp_bintool = None
self.elf_fname = None
self.auto_write_symbols = auto_write_symbols
self.absent = False
self.optional = False
self.overlap = False
self.elf_base_sym = None
self.offset_from_elf = None
self.preserve = False
self.build_done = False
self.no_write_symbols = False
self.symbols_base = None
@staticmethod
def FindEntryClass(etype, expanded):
"""Look up the entry class for a node.
Args:
node_node: Path name of Node object containing information about
the entry to create (used for errors)
etype: Entry type to use
expanded: Use the expanded version of etype
Returns:
The entry class object if found, else None if not found and expanded
is True, else a tuple:
module name that could not be found
exception received
"""
# Convert something like 'u-boot@0' to 'u_boot' since we are only
# interested in the type.
module_name = etype.replace('-', '_')
if '@' in module_name:
module_name = module_name.split('@')[0]
if expanded:
module_name += '_expanded'
module = modules.get(module_name)
# Also allow entry-type modules to be brought in from the etype directory.
# Import the module if we have not already done so.
if not module:
try:
module = importlib.import_module('binman.etype.' + module_name)
except ImportError as e:
if expanded:
return None
return module_name, e
modules[module_name] = module
# Look up the expected class name
return getattr(module, 'Entry_%s' % module_name)
@staticmethod
def Lookup(node_path, etype, expanded, missing_etype=False):
"""Look up the entry class for a node.
Args:
node_node (str): Path name of Node object containing information
about the entry to create (used for errors)
etype (str): Entry type to use
expanded (bool): Use the expanded version of etype
missing_etype (bool): True to default to a blob etype if the
requested etype is not found
Returns:
The entry class object if found, else None if not found and expanded
is True
Raise:
ValueError if expanded is False and the class is not found
"""
# Convert something like 'u-boot@0' to 'u_boot' since we are only
# interested in the type.
cls = Entry.FindEntryClass(etype, expanded)
if cls is None:
return None
elif isinstance(cls, tuple):
if missing_etype:
cls = Entry.FindEntryClass('blob', False)
if isinstance(cls, tuple): # This should not fail
module_name, e = cls
raise ValueError(
"Unknown entry type '%s' in node '%s' (expected etype/%s.py, error '%s'" %
(etype, node_path, module_name, e))
return cls
@staticmethod
def Create(section, node, etype=None, expanded=False, missing_etype=False):
"""Create a new entry for a node.
Args:
section (entry_Section): Section object containing this node
node (Node): Node object containing information about the entry to
create
etype (str): Entry type to use, or None to work it out (used for
tests)
expanded (bool): Use the expanded version of etype
missing_etype (bool): True to default to a blob etype if the
requested etype is not found
Returns:
A new Entry object of the correct type (a subclass of Entry)
"""
if not etype:
etype = fdt_util.GetString(node, 'type', node.name)
obj = Entry.Lookup(node.path, etype, expanded, missing_etype)
if obj and expanded:
# Check whether to use the expanded entry
new_etype = etype + '-expanded'
can_expand = not fdt_util.GetBool(node, 'no-expanded')
if can_expand and obj.UseExpanded(node, etype, new_etype):
etype = new_etype
else:
obj = None
if not obj:
obj = Entry.Lookup(node.path, etype, False, missing_etype)
# Call its constructor to get the object we want.
return obj(section, etype, node)
def ReadNode(self):
"""Read entry information from the node
This must be called as the first thing after the Entry is created.
This reads all the fields we recognise from the node, ready for use.
"""
self.ensure_props()
if 'pos' in self._node.props:
self.Raise("Please use 'offset' instead of 'pos'")
if 'expand-size' in self._node.props:
self.Raise("Please use 'extend-size' instead of 'expand-size'")
self.offset = fdt_util.GetInt(self._node, 'offset')
self.size = fdt_util.GetInt(self._node, 'size')
self.min_size = fdt_util.GetInt(self._node, 'min-size', 0)
self.orig_offset = fdt_util.GetInt(self._node, 'orig-offset')
self.orig_size = fdt_util.GetInt(self._node, 'orig-size')
if self.GetImage().copy_to_orig:
self.orig_offset = self.offset
self.orig_size = self.size
# These should not be set in input files, but are set in an FDT map,
# which is also read by this code.
self.image_pos = fdt_util.GetInt(self._node, 'image-pos')
self.uncomp_size = fdt_util.GetInt(self._node, 'uncomp-size')
self.align = fdt_util.GetInt(self._node, 'align')
if tools.not_power_of_two(self.align):
raise ValueError("Node '%s': Alignment %s must be a power of two" %
(self._node.path, self.align))
if self.section and self.align is None:
self.align = self.section.align_default
self.pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
self.pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
self.align_size = fdt_util.GetInt(self._node, 'align-size')
if tools.not_power_of_two(self.align_size):
self.Raise("Alignment size %s must be a power of two" %
self.align_size)
self.align_end = fdt_util.GetInt(self._node, 'align-end')
self.offset_unset = fdt_util.GetBool(self._node, 'offset-unset')
self.extend_size = fdt_util.GetBool(self._node, 'extend-size')
self.missing_msg = fdt_util.GetString(self._node, 'missing-msg')
self.optional = fdt_util.GetBool(self._node, 'optional')
self.overlap = fdt_util.GetBool(self._node, 'overlap')
if self.overlap:
self.required_props += ['offset', 'size']
self.assume_size = fdt_util.GetInt(self._node, 'assume-size', 0)
# This is only supported by blobs and sections at present
self.compress = fdt_util.GetString(self._node, 'compress', 'none')
self.offset_from_elf = fdt_util.GetPhandleNameOffset(self._node,
'offset-from-elf')
self.preserve = fdt_util.GetBool(self._node, 'preserve')
self.no_write_symbols = fdt_util.GetBool(self._node, 'no-write-symbols')
self.symbols_base = fdt_util.GetInt(self._node, 'symbols-base')
def GetDefaultFilename(self):
return None
def GetFdts(self):
"""Get the device trees used by this entry
Returns:
Empty dict, if this entry is not a .dtb, otherwise:
Dict:
key: Filename from this entry (without the path)
value: Tuple:
Entry object for this dtb
Filename of file containing this dtb
"""
return {}
def gen_entries(self):
"""Allow entries to generate other entries
Some entries generate subnodes automatically, from which sub-entries
are then created. This method allows those to be added to the binman
definition for the current image. An entry which implements this method
should call state.AddSubnode() to add a subnode and can add properties
with state.AddString(), etc.
An example is 'files', which produces a section containing a list of
files.
"""
pass
def AddMissingProperties(self, have_image_pos):
"""Add new properties to the device tree as needed for this entry
Args:
have_image_pos: True if this entry has an image position. This can
be False if its parent section is compressed, since compression
groups all entries together into a compressed block of data,
obscuring the start of each individual child entry
"""
for prop in ['offset', 'size']:
if not prop in self._node.props:
state.AddZeroProp(self._node, prop)
if have_image_pos and 'image-pos' not in self._node.props:
state.AddZeroProp(self._node, 'image-pos')
if self.GetImage().allow_repack:
if self.orig_offset is not None:
state.AddZeroProp(self._node, 'orig-offset', True)
if self.orig_size is not None:
state.AddZeroProp(self._node, 'orig-size', True)
if self.compress != 'none':
state.AddZeroProp(self._node, 'uncomp-size')
if self.update_hash:
err = state.CheckAddHashProp(self._node)
if err:
self.Raise(err)
def SetCalculatedProperties(self):
"""Set the value of device-tree properties calculated by binman"""
state.SetInt(self._node, 'offset', self.offset)
state.SetInt(self._node, 'size', self.size)
base = self.section.GetRootSkipAtStart() if self.section else 0
if self.image_pos is not None:
state.SetInt(self._node, 'image-pos', self.image_pos - base)
if self.GetImage().allow_repack:
if self.orig_offset is not None:
state.SetInt(self._node, 'orig-offset', self.orig_offset, True)
if self.orig_size is not None:
state.SetInt(self._node, 'orig-size', self.orig_size, True)
if self.uncomp_size is not None:
state.SetInt(self._node, 'uncomp-size', self.uncomp_size)
if self.update_hash:
state.CheckSetHashValue(self._node, self.GetData)
def ProcessFdt(self, fdt):
"""Allow entries to adjust the device tree
Some entries need to adjust the device tree for their purposes. This
may involve adding or deleting properties.
Returns:
True if processing is complete
False if processing could not be completed due to a dependency.
This will cause the entry to be retried after others have been
called
"""
return True
def SetPrefix(self, prefix):
"""Set the name prefix for a node
Args:
prefix: Prefix to set, or '' to not use a prefix
"""
if prefix:
self.name = prefix + self.name
def SetContents(self, data):
"""Set the contents of an entry
This sets both the data and content_size properties
Args:
data: Data to set to the contents (bytes)
"""
self.data = data
self.contents_size = len(self.data)
def ProcessContentsUpdate(self, data):
"""Update the contents of an entry, after the size is fixed
This checks that the new data is the same size as the old. If the size
has changed, this triggers a re-run of the packing algorithm.
Args:
data: Data to set to the contents (bytes)
Raises:
ValueError if the new data size is not the same as the old
"""
size_ok = True
new_size = len(data)
if state.AllowEntryExpansion() and new_size > self.contents_size:
# self.data will indicate the new size needed
size_ok = False
elif state.AllowEntryContraction() and new_size < self.contents_size:
size_ok = False
# If not allowed to change, try to deal with it or give up
if size_ok:
if new_size > self.contents_size:
self.Raise('Cannot update entry size from %d to %d' %
(self.contents_size, new_size))
# Don't let the data shrink. Pad it if necessary
if size_ok and new_size < self.contents_size:
data += tools.get_bytes(0, self.contents_size - new_size)
if not size_ok:
tout.debug("Entry '%s' size change from %s to %s" % (
self._node.path, to_hex(self.contents_size),
to_hex(new_size)))
self.SetContents(data)
return size_ok
def ObtainContents(self, skip_entry=None, fake_size=0):
"""Figure out the contents of an entry.
For missing blobs (where allow-missing is enabled), the contents are set
to b'' and self.missing is set to True.
Args:
skip_entry (Entry): Entry to skip when obtaining section contents
fake_size (int): Size of fake file to create if needed
Returns:
True if the contents were found, False if another call is needed
after the other entries are processed, None if there is no contents
"""
# No contents by default: subclasses can implement this
return True
def ResetForPack(self):
"""Reset offset/size fields so that packing can be done again"""
self.Detail('ResetForPack: offset %s->%s, size %s->%s' %
(to_hex(self.offset), to_hex(self.orig_offset),
to_hex(self.size), to_hex(self.orig_size)))
self.pre_reset_size = self.size
self.offset = self.orig_offset
self.size = self.orig_size
def Pack(self, offset):
"""Figure out how to pack the entry into the section
Most of the time the entries are not fully specified. There may be
an alignment but no size. In that case we take the size from the
contents of the entry.
If an entry has no hard-coded offset, it will be placed at @offset.
Once this function is complete, both the offset and size of the
entry will be know.
Args:
Current section offset pointer
Returns:
New section offset pointer (after this entry)
"""
self.Detail('Packing: offset=%s, size=%s, content_size=%x' %
(to_hex(self.offset), to_hex(self.size),
self.contents_size))
if self.offset is None:
if self.offset_unset:
self.Raise('No offset set with offset-unset: should another '
'entry provide this correct offset?')
elif self.offset_from_elf:
self.offset = self.lookup_offset()
else:
self.offset = tools.align(offset, self.align)
needed = self.pad_before + self.contents_size + self.pad_after
needed = max(needed, self.min_size)
needed = tools.align(needed, self.align_size)
size = self.size
if not size:
size = needed
new_offset = self.offset + size
aligned_offset = tools.align(new_offset, self.align_end)
if aligned_offset != new_offset:
size = aligned_offset - self.offset
new_offset = aligned_offset
if not self.size:
self.size = size
if self.size < needed:
self.Raise("Entry contents size is %#x (%d) but entry size is "
"%#x (%d)" % (needed, needed, self.size, self.size))
# Check that the alignment is correct. It could be wrong if the
# and offset or size values were provided (i.e. not calculated), but
# conflict with the provided alignment values
if self.size != tools.align(self.size, self.align_size):
self.Raise("Size %#x (%d) does not match align-size %#x (%d)" %
(self.size, self.size, self.align_size, self.align_size))
if self.offset != tools.align(self.offset, self.align):
self.Raise("Offset %#x (%d) does not match align %#x (%d)" %
(self.offset, self.offset, self.align, self.align))
self.Detail(' - packed: offset=%#x, size=%#x, content_size=%#x, next_offset=%x' %
(self.offset, self.size, self.contents_size, new_offset))
return new_offset
def Raise(self, msg):
"""Convenience function to raise an error referencing a node"""
raise ValueError("Node '%s': %s" % (self._node.path, msg))
def Info(self, msg):
"""Convenience function to log info referencing a node"""
tag = "Info '%s'" % self._node.path
tout.detail('%30s: %s' % (tag, msg))
def Detail(self, msg):
"""Convenience function to log detail referencing a node"""
tag = "Node '%s'" % self._node.path
tout.detail('%30s: %s' % (tag, msg))
def GetEntryArgsOrProps(self, props, required=False):
"""Return the values of a set of properties
Looks up the named entryargs and returns the value for each. If any
required ones are missing, the error is reported to the user.
Args:
props (list of EntryArg): List of entry arguments to look up
required (bool): True if these entry arguments are required
Returns:
list of values: one for each item in props, the type is determined
by the EntryArg's 'datatype' property (str or int)
Raises:
ValueError if a property is not found
"""
values = []
missing = []
for prop in props:
python_prop = prop.name.replace('-', '_')
if hasattr(self, python_prop):
value = getattr(self, python_prop)
else:
value = None
if value is None:
value = self.GetArg(prop.name, prop.datatype)
if value is None and required:
missing.append(prop.name)
values.append(value)
if missing:
self.GetImage().MissingArgs(self, missing)
return values
def GetPath(self):
"""Get the path of a node
Returns:
Full path of the node for this entry
"""
return self._node.path
def GetData(self, required=True):
"""Get the contents of an entry
Args:
required: True if the data must be present, False if it is OK to
return None
Returns:
bytes content of the entry, excluding any padding. If the entry is
compressed, the compressed data is returned. If the entry data
is not yet available, False can be returned. If the entry data
is null, then None is returned.
"""
self.Detail('GetData: size %s' % to_hex_size(self.data))
return self.data
def GetPaddedData(self, data=None):
"""Get the data for an entry including any padding
Gets the entry data and uses its section's pad-byte value to add padding
before and after as defined by the pad-before and pad-after properties.
This does not consider alignment.
Returns:
Contents of the entry along with any pad bytes before and
after it (bytes)
"""
if data is None:
data = self.GetData()
return self.section.GetPaddedDataForEntry(self, data)
def GetOffsets(self):
"""Get the offsets for siblings
Some entry types can contain information about the position or size of
other entries. An example of this is the Intel Flash Descriptor, which
knows where the Intel Management Engine section should go.
If this entry knows about the position of other entries, it can specify
this by returning values here
Returns:
Dict:
key: Entry type
value: List containing position and size of the given entry
type. Either can be None if not known
"""
return {}
def SetOffsetSize(self, offset, size):
"""Set the offset and/or size of an entry
Args:
offset: New offset, or None to leave alone
size: New size, or None to leave alone
"""
if offset is not None:
self.offset = offset
if size is not None:
self.size = size
def SetImagePos(self, image_pos):
"""Set the position in the image
Args:
image_pos: Position of this entry in the image
"""
self.image_pos = image_pos + self.offset
def ProcessContents(self):
"""Do any post-packing updates of entry contents
This function should call ProcessContentsUpdate() to update the entry
contents, if necessary, returning its return value here.
Args:
data: Data to set to the contents (bytes)
Returns:
True if the new data size is OK, False if expansion is needed
Raises:
ValueError if the new data size is not the same as the old and
state.AllowEntryExpansion() is False
"""
return True
def WriteSymbols(self, section):
"""Write symbol values into binary files for access at run time
As a special case, if symbols_base is not specified and this is an
end-at-4gb image, a symbols_base of 0 is used
Args:
section: Section containing the entry
"""
if self.auto_write_symbols and not self.no_write_symbols:
# Check if we are writing symbols into an ELF file
is_elf = self.GetDefaultFilename() == self.elf_fname
symbols_base = self.symbols_base
if symbols_base is None and self.GetImage()._end_4gb:
symbols_base = 0
elf.LookupAndWriteSymbols(self.elf_fname, self, section.GetImage(),
is_elf, self.elf_base_sym, symbols_base)
def CheckEntries(self):
"""Check that the entry offsets are correct
This is used for entries which have extra offset requirements (other
than having to be fully inside their section). Sub-classes can implement
this function and raise if there is a problem.
"""
pass
@staticmethod
def GetStr(value):
if value is None:
return '<none> '
return '%08x' % value
@staticmethod
def WriteMapLine(fd, indent, name, offset, size, image_pos):
print('%s %s%s %s %s' % (Entry.GetStr(image_pos), ' ' * indent,
Entry.GetStr(offset), Entry.GetStr(size),
name), file=fd)
def WriteMap(self, fd, indent):
"""Write a map of the entry to a .map file
Args:
fd: File to write the map to
indent: Curent indent level of map (0=none, 1=one level, etc.)
"""
self.WriteMapLine(fd, indent, self.name, self.offset, self.size,
self.image_pos)
# pylint: disable=assignment-from-none
def GetEntries(self):
"""Return a list of entries contained by this entry
Returns:
List of entries, or None if none. A normal entry has no entries
within it so will return None
"""
return None
def FindEntryByNode(self, find_node):
"""Find a node in an entry, searching all subentries
This does a recursive search.
Args:
find_node (fdt.Node): Node to find
Returns:
Entry: entry, if found, else None
"""
entries = self.GetEntries()
if entries:
for entry in entries.values():
if entry._node == find_node:
return entry
found = entry.FindEntryByNode(find_node)
if found:
return found
return None
def GetArg(self, name, datatype=str):
"""Get the value of an entry argument or device-tree-node property
Some node properties can be provided as arguments to binman. First check
the entry arguments, and fall back to the device tree if not found
Args:
name: Argument name
datatype: Data type (str or int)
Returns:
Value of argument as a string or int, or None if no value
Raises:
ValueError if the argument cannot be converted to in
"""
value = state.GetEntryArg(name)
if value is not None:
if datatype == int:
try:
value = int(value)
except ValueError:
self.Raise("Cannot convert entry arg '%s' (value '%s') to integer" %
(name, value))
elif datatype == str:
pass
else:
raise ValueError("GetArg() internal error: Unknown data type '%s'" %
datatype)
else:
value = fdt_util.GetDatatype(self._node, name, datatype)
return value
@staticmethod
def WriteDocs(modules, test_missing=None):
"""Write out documentation about the various entry types to stdout
Args:
modules: List of modules to include
test_missing: Used for testing. This is a module to report
as missing
"""
print('''Binman Entry Documentation
==========================
This file describes the entry types supported by binman. These entry types can
be placed in an image one by one to build up a final firmware image. It is
fairly easy to create new entry types. Just add a new file to the 'etype'
directory. You can use the existing entries as examples.
Note that some entries are subclasses of others, using and extending their
features to produce new behaviours.
''')
modules = sorted(modules)
# Don't show the test entry
if '_testing' in modules:
modules.remove('_testing')
missing = []
for name in modules:
module = Entry.Lookup('WriteDocs', name, False)
docs = getattr(module, '__doc__')
if test_missing == name:
docs = None
if docs:
lines = docs.splitlines()
first_line = lines[0]
rest = [line[4:] for line in lines[1:]]
hdr = 'Entry: %s: %s' % (name.replace('_', '-'), first_line)
# Create a reference for use by rST docs
ref_name = f'etype_{module.__name__[6:]}'.lower()
print('.. _%s:' % ref_name)
print()
print(hdr)
print('-' * len(hdr))
print('\n'.join(rest))
print()
print()
else:
missing.append(name)
if missing:
raise ValueError('Documentation is missing for modules: %s' %
', '.join(missing))
def GetUniqueName(self):
"""Get a unique name for a node
Returns:
String containing a unique name for a node, consisting of the name
of all ancestors (starting from within the 'binman' node) separated
by a dot ('.'). This can be useful for generating unique filesnames
in the output directory.
"""
name = self.name
node = self._node
while node.parent:
node = node.parent
if node.name in ('binman', '/'):
break
name = '%s.%s' % (node.name, name)
return name
def extend_to_limit(self, limit):
"""Extend an entry so that it ends at the given offset limit"""
if self.offset + self.size < limit:
self.size = limit - self.offset
# Request the contents again, since changing the size requires that
# the data grows. This should not fail, but check it to be sure.
if not self.ObtainContents():
self.Raise('Cannot obtain contents when expanding entry')
def HasSibling(self, name):
"""Check if there is a sibling of a given name
Returns:
True if there is an entry with this name in the the same section,
else False
"""
return name in self.section.GetEntries()
def GetSiblingImagePos(self, name):
"""Return the image position of the given sibling
Returns:
Image position of sibling, or None if the sibling has no position,
or False if there is no such sibling
"""
if not self.HasSibling(name):
return False
return self.section.GetEntries()[name].image_pos
@staticmethod
def AddEntryInfo(entries, indent, name, etype, size, image_pos,
uncomp_size, offset, entry):
"""Add a new entry to the entries list
Args:
entries: List (of EntryInfo objects) to add to
indent: Current indent level to add to list
name: Entry name (string)
etype: Entry type (string)
size: Entry size in bytes (int)
image_pos: Position within image in bytes (int)
uncomp_size: Uncompressed size if the entry uses compression, else
None
offset: Entry offset within parent in bytes (int)
entry: Entry object
"""
entries.append(EntryInfo(indent, name, etype, size, image_pos,
uncomp_size, offset, entry))
def ListEntries(self, entries, indent):
"""Add files in this entry to the list of entries
This can be overridden by subclasses which need different behaviour.
Args:
entries: List (of EntryInfo objects) to add to
indent: Current indent level to add to list
"""
self.AddEntryInfo(entries, indent, self.name, self.etype, self.size,
self.image_pos, self.uncomp_size, self.offset, self)
def ReadData(self, decomp=True, alt_format=None):
"""Read the data for an entry from the image
This is used when the image has been read in and we want to extract the
data for a particular entry from that image.
Args:
decomp: True to decompress any compressed data before returning it;
False to return the raw, uncompressed data
Returns:
Entry data (bytes)
"""
# Use True here so that we get an uncompressed section to work from,
# although compressed sections are currently not supported
tout.debug("ReadChildData section '%s', entry '%s'" %
(self.section.GetPath(), self.GetPath()))
data = self.section.ReadChildData(self, decomp, alt_format)
return data
def ReadChildData(self, child, decomp=True, alt_format=None):
"""Read the data for a particular child entry
This reads data from the parent and extracts the piece that relates to
the given child.
Args:
child (Entry): Child entry to read data for (must be valid)
decomp (bool): True to decompress any compressed data before
returning it; False to return the raw, uncompressed data
alt_format (str): Alternative format to read in, or None
Returns:
Data for the child (bytes)
"""
pass
def LoadData(self, decomp=True):
data = self.ReadData(decomp)
self.contents_size = len(data)
self.ProcessContentsUpdate(data)
self.Detail('Loaded data size %x' % len(data))
def GetAltFormat(self, data, alt_format):
"""Read the data for an extry in an alternative format
Supported formats are list in the documentation for each entry. An
example is fdtmap which provides .
Args:
data (bytes): Data to convert (this should have been produced by the
entry)
alt_format (str): Format to use
"""
pass
def GetImage(self):
"""Get the image containing this entry
Returns:
Image object containing this entry
"""
return self.section.GetImage()
def WriteData(self, data, decomp=True):
"""Write the data to an entry in the image
This is used when the image has been read in and we want to replace the
data for a particular entry in that image.
The image must be re-packed and written out afterwards.
Args:
data: Data to replace it with
decomp: True to compress the data if needed, False if data is
already compressed so should be used as is
Returns:
True if the data did not result in a resize of this entry, False if
the entry must be resized
"""
if self.size is not None:
self.contents_size = self.size
else:
self.contents_size = self.pre_reset_size
ok = self.ProcessContentsUpdate(data)
self.build_done = False
self.Detail('WriteData: size=%x, ok=%s' % (len(data), ok))
section_ok = self.section.WriteChildData(self)
return ok and section_ok
def WriteChildData(self, child):
"""Handle writing the data in a child entry
This should be called on the child's parent section after the child's
data has been updated. It should update any data structures needed to
validate that the update is successful.
This base-class implementation does nothing, since the base Entry object
does not have any children.
Args:
child: Child Entry that was written
Returns:
True if the section could be updated successfully, False if the
data is such that the section could not update
"""
self.build_done = False
entry = self.section
# Now we must rebuild all sections above this one
while entry and entry != entry.section:
self.build_done = False
entry = entry.section
return True
def GetSiblingOrder(self):
"""Get the relative order of an entry amoung its siblings
Returns:
'start' if this entry is first among siblings, 'end' if last,
otherwise None
"""
entries = list(self.section.GetEntries().values())
if entries:
if self == entries[0]:
return 'start'
elif self == entries[-1]:
return 'end'
return 'middle'
def SetAllowMissing(self, allow_missing):
"""Set whether a section allows missing external blobs
Args:
allow_missing: True if allowed, False if not allowed
"""
# This is meaningless for anything other than sections
pass
def SetAllowFakeBlob(self, allow_fake):
"""Set whether a section allows to create a fake blob
Args:
allow_fake: True if allowed, False if not allowed
"""
self.allow_fake = allow_fake
def CheckMissing(self, missing_list):
"""Check if the entry has missing external blobs
If there are missing (non-optional) blobs, the entries are added to the
list
Args:
missing_list: List of Entry objects to be added to
"""
if self.missing and not self.optional:
missing_list.append(self)
def check_fake_fname(self, fname, size=0):
"""If the file is missing and the entry allows fake blobs, fake it
Sets self.faked to True if faked
Args:
fname (str): Filename to check
size (int): Size of fake file to create
Returns:
tuple:
fname (str): Filename of faked file
bool: True if the blob was faked, False if not
"""
if self.allow_fake and not pathlib.Path(fname).is_file():
if not self.fake_fname:
outfname = os.path.join(self.fake_dir, os.path.basename(fname))
with open(outfname, "wb") as out:
out.truncate(size)
tout.info(f"Entry '{self._node.path}': Faked blob '{outfname}'")
self.fake_fname = outfname
self.faked = True
return self.fake_fname, True
return fname, False
def CheckFakedBlobs(self, faked_blobs_list):
"""Check if any entries in this section have faked external blobs
If there are faked blobs, the entries are added to the list
Args:
faked_blobs_list: List of Entry objects to be added to
"""
# This is meaningless for anything other than blobs
pass
def CheckOptional(self, optional_list):
"""Check if the entry has missing but optional external blobs
If there are missing (optional) blobs, the entries are added to the list
Args:
optional_list (list): List of Entry objects to be added to
"""
if self.missing and self.optional:
optional_list.append(self)
def GetAllowMissing(self):
"""Get whether a section allows missing external blobs
Returns:
True if allowed, False if not allowed
"""
return self.allow_missing
def record_missing_bintool(self, bintool):
"""Record a missing bintool that was needed to produce this entry
Args:
bintool (Bintool): Bintool that was missing
"""
if bintool not in self.missing_bintools:
self.missing_bintools.append(bintool)
def check_missing_bintools(self, missing_list):
"""Check if any entries in this section have missing bintools
If there are missing bintools, these are added to the list
Args:
missing_list: List of Bintool objects to be added to
"""
for bintool in self.missing_bintools:
if bintool not in missing_list:
missing_list.append(bintool)
def GetHelpTags(self):
"""Get the tags use for missing-blob help
Returns:
list of possible tags, most desirable first
"""
return list(filter(None, [self.missing_msg, self.name, self.etype]))
def CompressData(self, indata):
"""Compress data according to the entry's compression method
Args:
indata: Data to compress
Returns:
Compressed data
"""
self.uncomp_data = indata
if self.compress != 'none':
self.uncomp_size = len(indata)
if self.comp_bintool.is_present():
data = self.comp_bintool.compress(indata)
uniq = self.GetUniqueName()
fname = tools.get_output_filename(f'comp.{uniq}')
tools.write_file(fname, data)
else:
self.record_missing_bintool(self.comp_bintool)
data = tools.get_bytes(0, 1024)
else:
data = indata
return data
def DecompressData(self, indata):
"""Decompress data according to the entry's compression method
Args:
indata: Data to decompress
Returns:
Decompressed data
"""
if self.compress != 'none':
if self.comp_bintool.is_present():
data = self.comp_bintool.decompress(indata)
self.uncomp_size = len(data)
else:
self.record_missing_bintool(self.comp_bintool)
data = tools.get_bytes(0, 1024)
else:
data = indata
self.uncomp_data = data
return data
@classmethod
def UseExpanded(cls, node, etype, new_etype):
"""Check whether to use an expanded entry type
This is called by Entry.Create() when it finds an expanded version of
an entry type (e.g. 'u-boot-expanded'). If this method returns True then
it will be used (e.g. in place of 'u-boot'). If it returns False, it is
ignored.
Args:
node: Node object containing information about the entry to
create
etype: Original entry type being used
new_etype: New entry type proposed
Returns:
True to use this entry type, False to use the original one
"""
tout.info("Node '%s': etype '%s': %s selected" %
(node.path, etype, new_etype))
return True
def CheckAltFormats(self, alt_formats):
"""Add any alternative formats supported by this entry type
Args:
alt_formats (dict): Dict to add alt_formats to:
key: Name of alt format
value: Help text
"""
pass
def AddBintools(self, btools):
"""Add the bintools used by this entry type
Args:
btools (dict of Bintool):
Raise:
ValueError if compression algorithm is not supported
"""
algo = self.compress
if algo != 'none':
algos = ['bzip2', 'gzip', 'lz4', 'lzma', 'lzo', 'xz', 'zstd']
if algo not in algos:
raise ValueError("Unknown algorithm '%s'" % algo)
names = {'lzma': 'lzma_alone', 'lzo': 'lzop'}
name = names.get(self.compress, self.compress)
self.comp_bintool = self.AddBintool(btools, name)
@classmethod
def AddBintool(self, tools, name):
"""Add a new bintool to the tools used by this etype
Args:
name: Name of the tool
"""
btool = bintool.Bintool.create(name)
tools[name] = btool
return btool
def SetUpdateHash(self, update_hash):
"""Set whether this entry's "hash" subnode should be updated
Args:
update_hash: True if hash should be updated, False if not
"""
self.update_hash = update_hash
def collect_contents_to_file(self, entries, prefix, fake_size=0):
"""Put the contents of a list of entries into a file
Args:
entries (list of Entry): Entries to collect
prefix (str): Filename prefix of file to write to
fake_size (int): Size of fake file to create if needed
If any entry does not have contents yet, this function returns False
for the data.
Returns:
Tuple:
bytes: Concatenated data from all the entries (or None)
str: Filename of file written (or None if no data)
str: Unique portion of filename (or None if no data)
"""
data = b''
for entry in entries:
data += entry.GetData()
uniq = self.GetUniqueName()
fname = tools.get_output_filename(f'{prefix}.{uniq}')
tools.write_file(fname, data)
return data, fname, uniq
@classmethod
def create_fake_dir(cls):
"""Create the directory for fake files"""
cls.fake_dir = tools.get_output_filename('binman-fake')
if not os.path.exists(cls.fake_dir):
os.mkdir(cls.fake_dir)
tout.notice(f"Fake-blob dir is '{cls.fake_dir}'")
def ensure_props(self):
"""Raise an exception if properties are missing
Args:
prop_list (list of str): List of properties to check for
Raises:
ValueError: Any property is missing
"""
not_present = []
for prop in self.required_props:
if not prop in self._node.props:
not_present.append(prop)
if not_present:
self.Raise(f"'{self.etype}' entry is missing properties: {' '.join(not_present)}")
def mark_absent(self, msg):
tout.info("Entry '%s' marked absent: %s" % (self._node.path, msg))
self.absent = True
def read_elf_segments(self):
"""Read segments from an entry that can generate an ELF file
Returns:
tuple:
list of segments, each:
int: Segment number (0 = first)
int: Start address of segment in memory
bytes: Contents of segment
int: entry address of ELF file
"""
return None
def lookup_offset(self):
node, sym_name, offset = self.offset_from_elf
entry = self.section.FindEntryByNode(node)
if not entry:
self.Raise("Cannot find entry for node '%s'" % node.name)
if not entry.elf_fname:
entry.Raise("Need elf-fname property '%s'" % node.name)
val = elf.GetSymbolOffset(entry.elf_fname, sym_name,
entry.elf_base_sym)
return val + offset
def mark_build_done(self):
"""Mark an entry as already built"""
self.build_done = True
entries = self.GetEntries()
if entries:
for entry in entries.values():
entry.mark_build_done()
def UpdateSignatures(self, privatekey_fname, algo, input_fname):
self.Raise('Updating signatures is not supported with this entry type')
def FdtContents(self, fdt_etype):
"""Get the contents of an FDT for a particular phase
Args:
fdt_etype (str): Filename of the phase of the FDT to return, e.g.
'u-boot-tpl-dtb'
Returns:
tuple:
fname (str): Filename of .dtb
bytes: Contents of FDT (possibly run through fdtgrep)
"""
return self.section.FdtContents(fdt_etype)
|