binman: Refactor much of the image code into 'section'
We want to support multiple sections within a single image. To do this, move most of the Image class implementation into a new Section class. An Image contains only a single Section, but at some point we will support a new 'section' entry, thus allowing Sections within Sections. Use the name 'bsection' for the module so we can use 'section' for the etype module. Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
		
							parent
							
								
									dd57c13bbc
								
							
						
					
					
						commit
						8f1da50ccc
					
				|  | @ -0,0 +1,302 @@ | ||||||
|  | # SPDX-License-Identifier: GPL-2.0+ | ||||||
|  | # Copyright (c) 2018 Google, Inc | ||||||
|  | # Written by Simon Glass <sjg@chromium.org> | ||||||
|  | # | ||||||
|  | # Base class for sections (collections of entries) | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | from __future__ import print_function | ||||||
|  | 
 | ||||||
|  | from collections import OrderedDict | ||||||
|  | import sys | ||||||
|  | 
 | ||||||
|  | import fdt_util | ||||||
|  | import re | ||||||
|  | import tools | ||||||
|  | 
 | ||||||
|  | class Section(object): | ||||||
|  |     """A section which contains multiple entries | ||||||
|  | 
 | ||||||
|  |     A section represents a collection of entries. There must be one or more | ||||||
|  |     sections in an image. Sections are used to group entries together. | ||||||
|  | 
 | ||||||
|  |     Attributes: | ||||||
|  |         _node: Node object that contains the section definition in device tree | ||||||
|  |         _size: Section size in bytes, or None if not known yet | ||||||
|  |         _align_size: Section size alignment, or None | ||||||
|  |         _pad_before: Number of bytes before the first entry starts. This | ||||||
|  |             effectively changes the place where entry position 0 starts | ||||||
|  |         _pad_after: Number of bytes after the last entry ends. The last | ||||||
|  |             entry will finish on or before this boundary | ||||||
|  |         _pad_byte: Byte to use to pad the section where there is no entry | ||||||
|  |         _sort: True if entries should be sorted by position, False if they | ||||||
|  |             must be in-order in the device tree description | ||||||
|  |         _skip_at_start: Number of bytes before the first entry starts. These | ||||||
|  |             effectively adjust the starting position of entries. For example, | ||||||
|  |             if _pad_before is 16, then the first entry would start at 16. | ||||||
|  |             An entry with pos = 20 would in fact be written at position 4 | ||||||
|  |             in the image file. | ||||||
|  |         _end_4gb: Indicates that the section ends at the 4GB boundary. This is | ||||||
|  |             used for x86 images, which want to use positions such that a | ||||||
|  |              memory address (like 0xff800000) is the first entry position. | ||||||
|  |              This causes _skip_at_start to be set to the starting memory | ||||||
|  |              address. | ||||||
|  |         _entries: OrderedDict() of entries | ||||||
|  |     """ | ||||||
|  |     def __init__(self, name, node, test=False): | ||||||
|  |         global entry | ||||||
|  |         global Entry | ||||||
|  |         import entry | ||||||
|  |         from entry import Entry | ||||||
|  | 
 | ||||||
|  |         self._node = node | ||||||
|  |         self._size = None | ||||||
|  |         self._align_size = None | ||||||
|  |         self._pad_before = 0 | ||||||
|  |         self._pad_after = 0 | ||||||
|  |         self._pad_byte = 0 | ||||||
|  |         self._sort = False | ||||||
|  |         self._skip_at_start = 0 | ||||||
|  |         self._end_4gb = False | ||||||
|  |         self._entries = OrderedDict() | ||||||
|  |         if not test: | ||||||
|  |             self._ReadNode() | ||||||
|  |             self._ReadEntries() | ||||||
|  | 
 | ||||||
|  |     def _ReadNode(self): | ||||||
|  |         """Read properties from the section node""" | ||||||
|  |         self._size = fdt_util.GetInt(self._node, 'size') | ||||||
|  |         self._align_size = fdt_util.GetInt(self._node, 'align-size') | ||||||
|  |         if tools.NotPowerOfTwo(self._align_size): | ||||||
|  |             self._Raise("Alignment size %s must be a power of two" % | ||||||
|  |                         self._align_size) | ||||||
|  |         self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0) | ||||||
|  |         self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0) | ||||||
|  |         self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0) | ||||||
|  |         self._sort = fdt_util.GetBool(self._node, 'sort-by-pos') | ||||||
|  |         self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb') | ||||||
|  |         if self._end_4gb and not self._size: | ||||||
|  |             self._Raise("Section size must be provided when using end-at-4gb") | ||||||
|  |         if self._end_4gb: | ||||||
|  |             self._skip_at_start = 0x100000000 - self._size | ||||||
|  | 
 | ||||||
|  |     def _ReadEntries(self): | ||||||
|  |         for node in self._node.subnodes: | ||||||
|  |             self._entries[node.name] = Entry.Create(self, node) | ||||||
|  | 
 | ||||||
|  |     def CheckSize(self): | ||||||
|  |         """Check that the section contents does not exceed its size, etc.""" | ||||||
|  |         contents_size = 0 | ||||||
|  |         for entry in self._entries.values(): | ||||||
|  |             contents_size = max(contents_size, entry.pos + entry.size) | ||||||
|  | 
 | ||||||
|  |         contents_size -= self._skip_at_start | ||||||
|  | 
 | ||||||
|  |         size = self._size | ||||||
|  |         if not size: | ||||||
|  |             size = self._pad_before + contents_size + self._pad_after | ||||||
|  |             size = tools.Align(size, self._align_size) | ||||||
|  | 
 | ||||||
|  |         if self._size and contents_size > self._size: | ||||||
|  |             self._Raise("contents size %#x (%d) exceeds section size %#x (%d)" % | ||||||
|  |                        (contents_size, contents_size, self._size, self._size)) | ||||||
|  |         if not self._size: | ||||||
|  |             self._size = size | ||||||
|  |         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)) | ||||||
|  |         return size | ||||||
|  | 
 | ||||||
|  |     def _Raise(self, msg): | ||||||
|  |         """Raises an error for this section | ||||||
|  | 
 | ||||||
|  |         Args: | ||||||
|  |             msg: Error message to use in the raise string | ||||||
|  |         Raises: | ||||||
|  |             ValueError() | ||||||
|  |         """ | ||||||
|  |         raise ValueError("Section '%s': %s" % (self._node.path, msg)) | ||||||
|  | 
 | ||||||
|  |     def GetPath(self): | ||||||
|  |         """Get the path of an image (in the FDT) | ||||||
|  | 
 | ||||||
|  |         Returns: | ||||||
|  |             Full path of the node for this image | ||||||
|  |         """ | ||||||
|  |         return self._node.path | ||||||
|  | 
 | ||||||
|  |     def FindEntryType(self, etype): | ||||||
|  |         """Find an entry type in the section | ||||||
|  | 
 | ||||||
|  |         Args: | ||||||
|  |             etype: Entry type to find | ||||||
|  |         Returns: | ||||||
|  |             entry matching that type, or None if not found | ||||||
|  |         """ | ||||||
|  |         for entry in self._entries.values(): | ||||||
|  |             if entry.etype == etype: | ||||||
|  |                 return entry | ||||||
|  |         return None | ||||||
|  | 
 | ||||||
|  |     def GetEntryContents(self): | ||||||
|  |         """Call ObtainContents() for each entry | ||||||
|  | 
 | ||||||
|  |         This calls each entry's ObtainContents() a few times until they all | ||||||
|  |         return True. We stop calling an entry's function once it returns | ||||||
|  |         True. This allows the contents of one entry to depend on another. | ||||||
|  | 
 | ||||||
|  |         After 3 rounds we give up since it's likely an error. | ||||||
|  |         """ | ||||||
|  |         todo = self._entries.values() | ||||||
|  |         for passnum in range(3): | ||||||
|  |             next_todo = [] | ||||||
|  |             for entry in todo: | ||||||
|  |                 if not entry.ObtainContents(): | ||||||
|  |                     next_todo.append(entry) | ||||||
|  |             todo = next_todo | ||||||
|  |             if not todo: | ||||||
|  |                 break | ||||||
|  | 
 | ||||||
|  |     def _SetEntryPosSize(self, name, pos, size): | ||||||
|  |         """Set the position and size of an entry | ||||||
|  | 
 | ||||||
|  |         Args: | ||||||
|  |             name: Entry name to update | ||||||
|  |             pos: New position | ||||||
|  |             size: New size | ||||||
|  |         """ | ||||||
|  |         entry = self._entries.get(name) | ||||||
|  |         if not entry: | ||||||
|  |             self._Raise("Unable to set pos/size for unknown entry '%s'" % name) | ||||||
|  |         entry.SetPositionSize(self._skip_at_start + pos, size) | ||||||
|  | 
 | ||||||
|  |     def GetEntryPositions(self): | ||||||
|  |         """Handle entries that want to set the position/size of other entries | ||||||
|  | 
 | ||||||
|  |         This calls each entry's GetPositions() method. If it returns a list | ||||||
|  |         of entries to update, it updates them. | ||||||
|  |         """ | ||||||
|  |         for entry in self._entries.values(): | ||||||
|  |             pos_dict = entry.GetPositions() | ||||||
|  |             for name, info in pos_dict.iteritems(): | ||||||
|  |                 self._SetEntryPosSize(name, *info) | ||||||
|  | 
 | ||||||
|  |     def PackEntries(self): | ||||||
|  |         """Pack all entries into the section""" | ||||||
|  |         pos = self._skip_at_start | ||||||
|  |         for entry in self._entries.values(): | ||||||
|  |             pos = entry.Pack(pos) | ||||||
|  | 
 | ||||||
|  |     def _SortEntries(self): | ||||||
|  |         """Sort entries by position""" | ||||||
|  |         entries = sorted(self._entries.values(), key=lambda entry: entry.pos) | ||||||
|  |         self._entries.clear() | ||||||
|  |         for entry in entries: | ||||||
|  |             self._entries[entry._node.name] = entry | ||||||
|  | 
 | ||||||
|  |     def CheckEntries(self): | ||||||
|  |         """Check that entries do not overlap or extend outside the section""" | ||||||
|  |         if self._sort: | ||||||
|  |             self._SortEntries() | ||||||
|  |         pos = 0 | ||||||
|  |         prev_name = 'None' | ||||||
|  |         for entry in self._entries.values(): | ||||||
|  |             if (entry.pos < self._skip_at_start or | ||||||
|  |                 entry.pos >= self._skip_at_start + self._size): | ||||||
|  |                 entry.Raise("Position %#x (%d) is outside the section starting " | ||||||
|  |                             "at %#x (%d)" % | ||||||
|  |                             (entry.pos, entry.pos, self._skip_at_start, | ||||||
|  |                              self._skip_at_start)) | ||||||
|  |             if entry.pos < pos: | ||||||
|  |                 entry.Raise("Position %#x (%d) overlaps with previous entry '%s' " | ||||||
|  |                             "ending at %#x (%d)" % | ||||||
|  |                             (entry.pos, entry.pos, prev_name, pos, pos)) | ||||||
|  |             pos = entry.pos + entry.size | ||||||
|  |             prev_name = entry.GetPath() | ||||||
|  | 
 | ||||||
|  |     def ProcessEntryContents(self): | ||||||
|  |         """Call the ProcessContents() method for each entry | ||||||
|  | 
 | ||||||
|  |         This is intended to adjust the contents as needed by the entry type. | ||||||
|  |         """ | ||||||
|  |         for entry in self._entries.values(): | ||||||
|  |             entry.ProcessContents() | ||||||
|  | 
 | ||||||
|  |     def WriteSymbols(self): | ||||||
|  |         """Write symbol values into binary files for access at run time""" | ||||||
|  |         for entry in self._entries.values(): | ||||||
|  |             entry.WriteSymbols(self) | ||||||
|  | 
 | ||||||
|  |     def BuildSection(self, fd, base_pos): | ||||||
|  |         """Write the section to a file""" | ||||||
|  |         fd.seek(base_pos) | ||||||
|  |         fd.write(self.GetData()) | ||||||
|  | 
 | ||||||
|  |     def GetData(self): | ||||||
|  |         """Write the section to a file""" | ||||||
|  |         section_data = chr(self._pad_byte) * self._size | ||||||
|  | 
 | ||||||
|  |         for entry in self._entries.values(): | ||||||
|  |             data = entry.GetData() | ||||||
|  |             base = self._pad_before + entry.pos - self._skip_at_start | ||||||
|  |             section_data = (section_data[:base] + data + | ||||||
|  |                             section_data[base + len(data):]) | ||||||
|  |         return section_data | ||||||
|  | 
 | ||||||
|  |     def LookupSymbol(self, sym_name, optional, msg): | ||||||
|  |         """Look up a symbol in an ELF file | ||||||
|  | 
 | ||||||
|  |         Looks up a symbol in an ELF file. Only entry types which come from an | ||||||
|  |         ELF image can be used by this function. | ||||||
|  | 
 | ||||||
|  |         At present the only entry property supported is pos. | ||||||
|  | 
 | ||||||
|  |         Args: | ||||||
|  |             sym_name: Symbol name in the ELF file to look up in the format | ||||||
|  |                 _binman_<entry>_prop_<property> where <entry> is the name of | ||||||
|  |                 the entry and <property> is the property to find (e.g. | ||||||
|  |                 _binman_u_boot_prop_pos). As a special case, you can append | ||||||
|  |                 _any to <entry> to have it search for any matching entry. E.g. | ||||||
|  |                 _binman_u_boot_any_prop_pos will match entries called u-boot, | ||||||
|  |                 u-boot-img and u-boot-nodtb) | ||||||
|  |             optional: True if the symbol is optional. If False this function | ||||||
|  |                 will raise if the symbol is not found | ||||||
|  |             msg: Message to display if an error occurs | ||||||
|  | 
 | ||||||
|  |         Returns: | ||||||
|  |             Value that should be assigned to that symbol, or None if it was | ||||||
|  |                 optional and not found | ||||||
|  | 
 | ||||||
|  |         Raises: | ||||||
|  |             ValueError if the symbol is invalid or not found, or references a | ||||||
|  |                 property which is not supported | ||||||
|  |         """ | ||||||
|  |         m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name) | ||||||
|  |         if not m: | ||||||
|  |             raise ValueError("%s: Symbol '%s' has invalid format" % | ||||||
|  |                              (msg, sym_name)) | ||||||
|  |         entry_name, prop_name = m.groups() | ||||||
|  |         entry_name = entry_name.replace('_', '-') | ||||||
|  |         entry = self._entries.get(entry_name) | ||||||
|  |         if not entry: | ||||||
|  |             if entry_name.endswith('-any'): | ||||||
|  |                 root = entry_name[:-4] | ||||||
|  |                 for name in self._entries: | ||||||
|  |                     if name.startswith(root): | ||||||
|  |                         rest = name[len(root):] | ||||||
|  |                         if rest in ['', '-img', '-nodtb']: | ||||||
|  |                             entry = self._entries[name] | ||||||
|  |         if not entry: | ||||||
|  |             err = ("%s: Entry '%s' not found in list (%s)" % | ||||||
|  |                    (msg, entry_name, ','.join(self._entries.keys()))) | ||||||
|  |             if optional: | ||||||
|  |                 print('Warning: %s' % err, file=sys.stderr) | ||||||
|  |                 return None | ||||||
|  |             raise ValueError(err) | ||||||
|  |         if prop_name == 'pos': | ||||||
|  |             return entry.pos | ||||||
|  |         else: | ||||||
|  |             raise ValueError("%s: No such property '%s'" % (msg, prop_name)) | ||||||
|  | 
 | ||||||
|  |     def GetEntries(self): | ||||||
|  |         return self._entries | ||||||
|  | @ -413,7 +413,7 @@ class TestFunctional(unittest.TestCase): | ||||||
|         self.assertEqual(0, retcode) |         self.assertEqual(0, retcode) | ||||||
|         self.assertIn('image', control.images) |         self.assertIn('image', control.images) | ||||||
|         image = control.images['image'] |         image = control.images['image'] | ||||||
|         entries = image._entries |         entries = image.GetEntries() | ||||||
|         self.assertEqual(5, len(entries)) |         self.assertEqual(5, len(entries)) | ||||||
| 
 | 
 | ||||||
|         # First u-boot |         # First u-boot | ||||||
|  | @ -456,7 +456,7 @@ class TestFunctional(unittest.TestCase): | ||||||
|         self.assertEqual(0, retcode) |         self.assertEqual(0, retcode) | ||||||
|         self.assertIn('image', control.images) |         self.assertIn('image', control.images) | ||||||
|         image = control.images['image'] |         image = control.images['image'] | ||||||
|         entries = image._entries |         entries = image.GetEntries() | ||||||
|         self.assertEqual(5, len(entries)) |         self.assertEqual(5, len(entries)) | ||||||
| 
 | 
 | ||||||
|         # First u-boot with padding before and after |         # First u-boot with padding before and after | ||||||
|  | @ -540,7 +540,7 @@ class TestFunctional(unittest.TestCase): | ||||||
|         """Test that entries which overflow the image size are detected""" |         """Test that entries which overflow the image size are detected""" | ||||||
|         with self.assertRaises(ValueError) as e: |         with self.assertRaises(ValueError) as e: | ||||||
|             self._DoTestFile('16_pack_image_overflow.dts') |             self._DoTestFile('16_pack_image_overflow.dts') | ||||||
|         self.assertIn("Image '/binman': contents size 0x4 (4) exceeds image " |         self.assertIn("Section '/binman': contents size 0x4 (4) exceeds section " | ||||||
|                       "size 0x3 (3)", str(e.exception)) |                       "size 0x3 (3)", str(e.exception)) | ||||||
| 
 | 
 | ||||||
|     def testPackImageSize(self): |     def testPackImageSize(self): | ||||||
|  | @ -563,14 +563,14 @@ class TestFunctional(unittest.TestCase): | ||||||
|         """Test that invalid image alignment is detected""" |         """Test that invalid image alignment is detected""" | ||||||
|         with self.assertRaises(ValueError) as e: |         with self.assertRaises(ValueError) as e: | ||||||
|             self._DoTestFile('19_pack_inv_image_align.dts') |             self._DoTestFile('19_pack_inv_image_align.dts') | ||||||
|         self.assertIn("Image '/binman': Size 0x7 (7) does not match " |         self.assertIn("Section '/binman': Size 0x7 (7) does not match " | ||||||
|                       "align-size 0x8 (8)", str(e.exception)) |                       "align-size 0x8 (8)", str(e.exception)) | ||||||
| 
 | 
 | ||||||
|     def testPackAlignPowerOf2(self): |     def testPackAlignPowerOf2(self): | ||||||
|         """Test that invalid image alignment is detected""" |         """Test that invalid image alignment is detected""" | ||||||
|         with self.assertRaises(ValueError) as e: |         with self.assertRaises(ValueError) as e: | ||||||
|             self._DoTestFile('20_pack_inv_image_align_power2.dts') |             self._DoTestFile('20_pack_inv_image_align_power2.dts') | ||||||
|         self.assertIn("Image '/binman': Alignment size 131 must be a power of " |         self.assertIn("Section '/binman': Alignment size 131 must be a power of " | ||||||
|                       "two", str(e.exception)) |                       "two", str(e.exception)) | ||||||
| 
 | 
 | ||||||
|     def testImagePadByte(self): |     def testImagePadByte(self): | ||||||
|  | @ -620,7 +620,7 @@ class TestFunctional(unittest.TestCase): | ||||||
|         """Test that the end-at-4gb property requires a size property""" |         """Test that the end-at-4gb property requires a size property""" | ||||||
|         with self.assertRaises(ValueError) as e: |         with self.assertRaises(ValueError) as e: | ||||||
|             self._DoTestFile('27_pack_4gb_no_size.dts') |             self._DoTestFile('27_pack_4gb_no_size.dts') | ||||||
|         self.assertIn("Image '/binman': Image size must be provided when " |         self.assertIn("Section '/binman': Section size must be provided when " | ||||||
|                       "using end-at-4gb", str(e.exception)) |                       "using end-at-4gb", str(e.exception)) | ||||||
| 
 | 
 | ||||||
|     def testPackX86RomOutside(self): |     def testPackX86RomOutside(self): | ||||||
|  | @ -628,7 +628,7 @@ class TestFunctional(unittest.TestCase): | ||||||
|         with self.assertRaises(ValueError) as e: |         with self.assertRaises(ValueError) as e: | ||||||
|             self._DoTestFile('28_pack_4gb_outside.dts') |             self._DoTestFile('28_pack_4gb_outside.dts') | ||||||
|         self.assertIn("Node '/binman/u-boot': Position 0x0 (0) is outside " |         self.assertIn("Node '/binman/u-boot': Position 0x0 (0) is outside " | ||||||
|                       "the image starting at 0xffffffe0 (4294967264)", |                       "the section starting at 0xffffffe0 (4294967264)", | ||||||
|                       str(e.exception)) |                       str(e.exception)) | ||||||
| 
 | 
 | ||||||
|     def testPackX86Rom(self): |     def testPackX86Rom(self): | ||||||
|  | @ -823,7 +823,7 @@ class TestFunctional(unittest.TestCase): | ||||||
|         """Test that microcode must be placed within the image""" |         """Test that microcode must be placed within the image""" | ||||||
|         with self.assertRaises(ValueError) as e: |         with self.assertRaises(ValueError) as e: | ||||||
|             self._DoReadFile('41_unknown_pos_size.dts', True) |             self._DoReadFile('41_unknown_pos_size.dts', True) | ||||||
|         self.assertIn("Image '/binman': Unable to set pos/size for unknown " |         self.assertIn("Section '/binman': Unable to set pos/size for unknown " | ||||||
|                 "entry 'invalid-entry'", str(e.exception)) |                 "entry 'invalid-entry'", str(e.exception)) | ||||||
| 
 | 
 | ||||||
|     def testPackFsp(self): |     def testPackFsp(self): | ||||||
|  |  | ||||||
|  | @ -13,6 +13,7 @@ import re | ||||||
| import sys | import sys | ||||||
| 
 | 
 | ||||||
| import fdt_util | import fdt_util | ||||||
|  | import bsection | ||||||
| import tools | import tools | ||||||
| 
 | 
 | ||||||
| class Image: | class Image: | ||||||
|  | @ -27,158 +28,31 @@ class Image: | ||||||
|         _node: Node object that contains the image definition in device tree |         _node: Node object that contains the image definition in device tree | ||||||
|         _name: Image name |         _name: Image name | ||||||
|         _size: Image size in bytes, or None if not known yet |         _size: Image size in bytes, or None if not known yet | ||||||
|         _align_size: Image size alignment, or None |  | ||||||
|         _pad_before: Number of bytes before the first entry starts. This |  | ||||||
|             effectively changes the place where entry position 0 starts |  | ||||||
|         _pad_after: Number of bytes after the last entry ends. The last |  | ||||||
|             entry will finish on or before this boundary |  | ||||||
|         _pad_byte: Byte to use to pad the image where there is no entry |  | ||||||
|         _filename: Output filename for image |         _filename: Output filename for image | ||||||
|         _sort: True if entries should be sorted by position, False if they |         _sections: Sections present in this image (may be one or more) | ||||||
|             must be in-order in the device tree description |  | ||||||
|         _skip_at_start: Number of bytes before the first entry starts. These |  | ||||||
|             effecively adjust the starting position of entries. For example, |  | ||||||
|             if _pad_before is 16, then the first entry would start at 16. |  | ||||||
|             An entry with pos = 20 would in fact be written at position 4 |  | ||||||
|             in the image file. |  | ||||||
|         _end_4gb: Indicates that the image ends at the 4GB boundary. This is |  | ||||||
|             used for x86 images, which want to use positions such that a |  | ||||||
|              memory address (like 0xff800000) is the first entry position. |  | ||||||
|              This causes _skip_at_start to be set to the starting memory |  | ||||||
|              address. |  | ||||||
|         _entries: OrderedDict() of entries |  | ||||||
|     """ |     """ | ||||||
|     def __init__(self, name, node, test=False): |     def __init__(self, name, node, test=False): | ||||||
|         global entry |  | ||||||
|         global Entry |  | ||||||
|         import entry |  | ||||||
|         from entry import Entry |  | ||||||
| 
 |  | ||||||
|         self._node = node |         self._node = node | ||||||
|         self._name = name |         self._name = name | ||||||
|         self._size = None |         self._size = None | ||||||
|         self._align_size = None |  | ||||||
|         self._pad_before = 0 |  | ||||||
|         self._pad_after = 0 |  | ||||||
|         self._pad_byte = 0 |  | ||||||
|         self._filename = '%s.bin' % self._name |         self._filename = '%s.bin' % self._name | ||||||
|         self._sort = False |         if test: | ||||||
|         self._skip_at_start = 0 |             self._section = bsection.Section('main-section', self._node, True) | ||||||
|         self._end_4gb = False |         else: | ||||||
|         self._entries = OrderedDict() |  | ||||||
| 
 |  | ||||||
|         if not test: |  | ||||||
|             self._ReadNode() |             self._ReadNode() | ||||||
|             self._ReadEntries() |  | ||||||
| 
 | 
 | ||||||
|     def _ReadNode(self): |     def _ReadNode(self): | ||||||
|         """Read properties from the image node""" |         """Read properties from the image node""" | ||||||
|         self._size = fdt_util.GetInt(self._node, 'size') |         self._size = fdt_util.GetInt(self._node, 'size') | ||||||
|         self._align_size = fdt_util.GetInt(self._node, 'align-size') |  | ||||||
|         if tools.NotPowerOfTwo(self._align_size): |  | ||||||
|             self._Raise("Alignment size %s must be a power of two" % |  | ||||||
|                         self._align_size) |  | ||||||
|         self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0) |  | ||||||
|         self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0) |  | ||||||
|         self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0) |  | ||||||
|         filename = fdt_util.GetString(self._node, 'filename') |         filename = fdt_util.GetString(self._node, 'filename') | ||||||
|         if filename: |         if filename: | ||||||
|             self._filename = filename |             self._filename = filename | ||||||
|         self._sort = fdt_util.GetBool(self._node, 'sort-by-pos') |         self._section = bsection.Section('main-section', self._node) | ||||||
|         self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb') |  | ||||||
|         if self._end_4gb and not self._size: |  | ||||||
|             self._Raise("Image size must be provided when using end-at-4gb") |  | ||||||
|         if self._end_4gb: |  | ||||||
|             self._skip_at_start = 0x100000000 - self._size |  | ||||||
| 
 |  | ||||||
|     def CheckSize(self): |  | ||||||
|         """Check that the image contents does not exceed its size, etc.""" |  | ||||||
|         contents_size = 0 |  | ||||||
|         for entry in self._entries.values(): |  | ||||||
|             contents_size = max(contents_size, entry.pos + entry.size) |  | ||||||
| 
 |  | ||||||
|         contents_size -= self._skip_at_start |  | ||||||
| 
 |  | ||||||
|         size = self._size |  | ||||||
|         if not size: |  | ||||||
|             size = self._pad_before + contents_size + self._pad_after |  | ||||||
|             size = tools.Align(size, self._align_size) |  | ||||||
| 
 |  | ||||||
|         if self._size and contents_size > self._size: |  | ||||||
|             self._Raise("contents size %#x (%d) exceeds image size %#x (%d)" % |  | ||||||
|                        (contents_size, contents_size, self._size, self._size)) |  | ||||||
|         if not self._size: |  | ||||||
|             self._size = size |  | ||||||
|         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)) |  | ||||||
| 
 |  | ||||||
|     def _Raise(self, msg): |  | ||||||
|         """Raises an error for this image |  | ||||||
| 
 |  | ||||||
|         Args: |  | ||||||
|             msg: Error message to use in the raise string |  | ||||||
|         Raises: |  | ||||||
|             ValueError() |  | ||||||
|         """ |  | ||||||
|         raise ValueError("Image '%s': %s" % (self._node.path, msg)) |  | ||||||
| 
 |  | ||||||
|     def GetPath(self): |  | ||||||
|         """Get the path of an image (in the FDT) |  | ||||||
| 
 |  | ||||||
|         Returns: |  | ||||||
|             Full path of the node for this image |  | ||||||
|         """ |  | ||||||
|         return self._node.path |  | ||||||
| 
 |  | ||||||
|     def _ReadEntries(self): |  | ||||||
|         for node in self._node.subnodes: |  | ||||||
|             self._entries[node.name] = Entry.Create(self, node) |  | ||||||
| 
 |  | ||||||
|     def FindEntryType(self, etype): |  | ||||||
|         """Find an entry type in the image |  | ||||||
| 
 |  | ||||||
|         Args: |  | ||||||
|             etype: Entry type to find |  | ||||||
|         Returns: |  | ||||||
|             entry matching that type, or None if not found |  | ||||||
|         """ |  | ||||||
|         for entry in self._entries.values(): |  | ||||||
|             if entry.etype == etype: |  | ||||||
|                 return entry |  | ||||||
|         return None |  | ||||||
| 
 | 
 | ||||||
|     def GetEntryContents(self): |     def GetEntryContents(self): | ||||||
|         """Call ObtainContents() for each entry |         """Call ObtainContents() for the section | ||||||
| 
 |  | ||||||
|         This calls each entry's ObtainContents() a few times until they all |  | ||||||
|         return True. We stop calling an entry's function once it returns |  | ||||||
|         True. This allows the contents of one entry to depend on another. |  | ||||||
| 
 |  | ||||||
|         After 3 rounds we give up since it's likely an error. |  | ||||||
|         """ |         """ | ||||||
|         todo = self._entries.values() |         self._section.GetEntryContents() | ||||||
|         for passnum in range(3): |  | ||||||
|             next_todo = [] |  | ||||||
|             for entry in todo: |  | ||||||
|                 if not entry.ObtainContents(): |  | ||||||
|                     next_todo.append(entry) |  | ||||||
|             todo = next_todo |  | ||||||
|             if not todo: |  | ||||||
|                 break |  | ||||||
| 
 |  | ||||||
|     def _SetEntryPosSize(self, name, pos, size): |  | ||||||
|         """Set the position and size of an entry |  | ||||||
| 
 |  | ||||||
|         Args: |  | ||||||
|             name: Entry name to update |  | ||||||
|             pos: New position |  | ||||||
|             size: New size |  | ||||||
|         """ |  | ||||||
|         entry = self._entries.get(name) |  | ||||||
|         if not entry: |  | ||||||
|             self._Raise("Unable to set pos/size for unknown entry '%s'" % name) |  | ||||||
|         entry.SetPositionSize(self._skip_at_start + pos, size) |  | ||||||
| 
 | 
 | ||||||
|     def GetEntryPositions(self): |     def GetEntryPositions(self): | ||||||
|         """Handle entries that want to set the position/size of other entries |         """Handle entries that want to set the position/size of other entries | ||||||
|  | @ -186,119 +60,36 @@ class Image: | ||||||
|         This calls each entry's GetPositions() method. If it returns a list |         This calls each entry's GetPositions() method. If it returns a list | ||||||
|         of entries to update, it updates them. |         of entries to update, it updates them. | ||||||
|         """ |         """ | ||||||
|         for entry in self._entries.values(): |         self._section.GetEntryPositions() | ||||||
|             pos_dict = entry.GetPositions() |  | ||||||
|             for name, info in pos_dict.iteritems(): |  | ||||||
|                 self._SetEntryPosSize(name, *info) |  | ||||||
| 
 | 
 | ||||||
|     def PackEntries(self): |     def PackEntries(self): | ||||||
|         """Pack all entries into the image""" |         """Pack all entries into the image""" | ||||||
|         pos = self._skip_at_start |         self._section.PackEntries() | ||||||
|         for entry in self._entries.values(): |  | ||||||
|             pos = entry.Pack(pos) |  | ||||||
| 
 | 
 | ||||||
|     def _SortEntries(self): |     def CheckSize(self): | ||||||
|         """Sort entries by position""" |         """Check that the image contents does not exceed its size, etc.""" | ||||||
|         entries = sorted(self._entries.values(), key=lambda entry: entry.pos) |         self._size = self._section.CheckSize() | ||||||
|         self._entries.clear() |  | ||||||
|         for entry in entries: |  | ||||||
|             self._entries[entry._node.name] = entry |  | ||||||
| 
 | 
 | ||||||
|     def CheckEntries(self): |     def CheckEntries(self): | ||||||
|         """Check that entries do not overlap or extend outside the image""" |         """Check that entries do not overlap or extend outside the image""" | ||||||
|         if self._sort: |         self._section.CheckEntries() | ||||||
|             self._SortEntries() |  | ||||||
|         pos = 0 |  | ||||||
|         prev_name = 'None' |  | ||||||
|         for entry in self._entries.values(): |  | ||||||
|             if (entry.pos < self._skip_at_start or |  | ||||||
|                 entry.pos >= self._skip_at_start + self._size): |  | ||||||
|                 entry.Raise("Position %#x (%d) is outside the image starting " |  | ||||||
|                             "at %#x (%d)" % |  | ||||||
|                             (entry.pos, entry.pos, self._skip_at_start, |  | ||||||
|                              self._skip_at_start)) |  | ||||||
|             if entry.pos < pos: |  | ||||||
|                 entry.Raise("Position %#x (%d) overlaps with previous entry '%s' " |  | ||||||
|                             "ending at %#x (%d)" % |  | ||||||
|                             (entry.pos, entry.pos, prev_name, pos, pos)) |  | ||||||
|             pos = entry.pos + entry.size |  | ||||||
|             prev_name = entry.GetPath() |  | ||||||
| 
 | 
 | ||||||
|     def ProcessEntryContents(self): |     def ProcessEntryContents(self): | ||||||
|         """Call the ProcessContents() method for each entry |         """Call the ProcessContents() method for each entry | ||||||
| 
 | 
 | ||||||
|         This is intended to adjust the contents as needed by the entry type. |         This is intended to adjust the contents as needed by the entry type. | ||||||
|         """ |         """ | ||||||
|         for entry in self._entries.values(): |         self._section.ProcessEntryContents() | ||||||
|             entry.ProcessContents() |  | ||||||
| 
 | 
 | ||||||
|     def WriteSymbols(self): |     def WriteSymbols(self): | ||||||
|         """Write symbol values into binary files for access at run time""" |         """Write symbol values into binary files for access at run time""" | ||||||
|         for entry in self._entries.values(): |         self._section.WriteSymbols() | ||||||
|             entry.WriteSymbols(self) |  | ||||||
| 
 | 
 | ||||||
|     def BuildImage(self): |     def BuildImage(self): | ||||||
|         """Write the image to a file""" |         """Write the image to a file""" | ||||||
|         fname = tools.GetOutputFilename(self._filename) |         fname = tools.GetOutputFilename(self._filename) | ||||||
|         with open(fname, 'wb') as fd: |         with open(fname, 'wb') as fd: | ||||||
|             fd.write(chr(self._pad_byte) * self._size) |             self._section.BuildSection(fd, 0) | ||||||
| 
 | 
 | ||||||
|             for entry in self._entries.values(): |     def GetEntries(self): | ||||||
|                 data = entry.GetData() |         return self._section.GetEntries() | ||||||
|                 fd.seek(self._pad_before + entry.pos - self._skip_at_start) |  | ||||||
|                 fd.write(data) |  | ||||||
| 
 |  | ||||||
|     def LookupSymbol(self, sym_name, optional, msg): |  | ||||||
|         """Look up a symbol in an ELF file |  | ||||||
| 
 |  | ||||||
|         Looks up a symbol in an ELF file. Only entry types which come from an |  | ||||||
|         ELF image can be used by this function. |  | ||||||
| 
 |  | ||||||
|         At present the only entry property supported is pos. |  | ||||||
| 
 |  | ||||||
|         Args: |  | ||||||
|             sym_name: Symbol name in the ELF file to look up in the format |  | ||||||
|                 _binman_<entry>_prop_<property> where <entry> is the name of |  | ||||||
|                 the entry and <property> is the property to find (e.g. |  | ||||||
|                 _binman_u_boot_prop_pos). As a special case, you can append |  | ||||||
|                 _any to <entry> to have it search for any matching entry. E.g. |  | ||||||
|                 _binman_u_boot_any_prop_pos will match entries called u-boot, |  | ||||||
|                 u-boot-img and u-boot-nodtb) |  | ||||||
|             optional: True if the symbol is optional. If False this function |  | ||||||
|                 will raise if the symbol is not found |  | ||||||
|             msg: Message to display if an error occurs |  | ||||||
| 
 |  | ||||||
|         Returns: |  | ||||||
|             Value that should be assigned to that symbol, or None if it was |  | ||||||
|                 optional and not found |  | ||||||
| 
 |  | ||||||
|         Raises: |  | ||||||
|             ValueError if the symbol is invalid or not found, or references a |  | ||||||
|                 property which is not supported |  | ||||||
|         """ |  | ||||||
|         m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name) |  | ||||||
|         if not m: |  | ||||||
|             raise ValueError("%s: Symbol '%s' has invalid format" % |  | ||||||
|                              (msg, sym_name)) |  | ||||||
|         entry_name, prop_name = m.groups() |  | ||||||
|         entry_name = entry_name.replace('_', '-') |  | ||||||
|         entry = self._entries.get(entry_name) |  | ||||||
|         if not entry: |  | ||||||
|             if entry_name.endswith('-any'): |  | ||||||
|                 root = entry_name[:-4] |  | ||||||
|                 for name in self._entries: |  | ||||||
|                     if name.startswith(root): |  | ||||||
|                         rest = name[len(root):] |  | ||||||
|                         if rest in ['', '-img', '-nodtb']: |  | ||||||
|                             entry = self._entries[name] |  | ||||||
|         if not entry: |  | ||||||
|             err = ("%s: Entry '%s' not found in list (%s)" % |  | ||||||
|                    (msg, entry_name, ','.join(self._entries.keys()))) |  | ||||||
|             if optional: |  | ||||||
|                 print('Warning: %s' % err, file=sys.stderr) |  | ||||||
|                 return None |  | ||||||
|             raise ValueError(err) |  | ||||||
|         if prop_name == 'pos': |  | ||||||
|             return entry.pos |  | ||||||
|         else: |  | ||||||
|             raise ValueError("%s: No such property '%s'" % (msg, prop_name)) |  | ||||||
|  |  | ||||||
|  | @ -12,25 +12,28 @@ from elf_test import capture_sys_output | ||||||
| class TestImage(unittest.TestCase): | class TestImage(unittest.TestCase): | ||||||
|     def testInvalidFormat(self): |     def testInvalidFormat(self): | ||||||
|         image = Image('name', 'node', test=True) |         image = Image('name', 'node', test=True) | ||||||
|  |         section = image._section | ||||||
|         with self.assertRaises(ValueError) as e: |         with self.assertRaises(ValueError) as e: | ||||||
|             image.LookupSymbol('_binman_something_prop_', False, 'msg') |             section.LookupSymbol('_binman_something_prop_', False, 'msg') | ||||||
|         self.assertIn( |         self.assertIn( | ||||||
|             "msg: Symbol '_binman_something_prop_' has invalid format", |             "msg: Symbol '_binman_something_prop_' has invalid format", | ||||||
|             str(e.exception)) |             str(e.exception)) | ||||||
| 
 | 
 | ||||||
|     def testMissingSymbol(self): |     def testMissingSymbol(self): | ||||||
|         image = Image('name', 'node', test=True) |         image = Image('name', 'node', test=True) | ||||||
|         image._entries = {} |         section = image._section | ||||||
|  |         section._entries = {} | ||||||
|         with self.assertRaises(ValueError) as e: |         with self.assertRaises(ValueError) as e: | ||||||
|             image.LookupSymbol('_binman_type_prop_pname', False, 'msg') |             section.LookupSymbol('_binman_type_prop_pname', False, 'msg') | ||||||
|         self.assertIn("msg: Entry 'type' not found in list ()", |         self.assertIn("msg: Entry 'type' not found in list ()", | ||||||
|                       str(e.exception)) |                       str(e.exception)) | ||||||
| 
 | 
 | ||||||
|     def testMissingSymbolOptional(self): |     def testMissingSymbolOptional(self): | ||||||
|         image = Image('name', 'node', test=True) |         image = Image('name', 'node', test=True) | ||||||
|         image._entries = {} |         section = image._section | ||||||
|  |         section._entries = {} | ||||||
|         with capture_sys_output() as (stdout, stderr): |         with capture_sys_output() as (stdout, stderr): | ||||||
|             val = image.LookupSymbol('_binman_type_prop_pname', True, 'msg') |             val = section.LookupSymbol('_binman_type_prop_pname', True, 'msg') | ||||||
|         self.assertEqual(val, None) |         self.assertEqual(val, None) | ||||||
|         self.assertEqual("Warning: msg: Entry 'type' not found in list ()\n", |         self.assertEqual("Warning: msg: Entry 'type' not found in list ()\n", | ||||||
|                          stderr.getvalue()) |                          stderr.getvalue()) | ||||||
|  | @ -38,7 +41,8 @@ class TestImage(unittest.TestCase): | ||||||
| 
 | 
 | ||||||
|     def testBadProperty(self): |     def testBadProperty(self): | ||||||
|         image = Image('name', 'node', test=True) |         image = Image('name', 'node', test=True) | ||||||
|         image._entries = {'u-boot': 1} |         section = image._section | ||||||
|  |         section._entries = {'u-boot': 1} | ||||||
|         with self.assertRaises(ValueError) as e: |         with self.assertRaises(ValueError) as e: | ||||||
|             image.LookupSymbol('_binman_u_boot_prop_bad', False, 'msg') |             section.LookupSymbol('_binman_u_boot_prop_bad', False, 'msg') | ||||||
|         self.assertIn("msg: No such property 'bad", str(e.exception)) |         self.assertIn("msg: No such property 'bad", str(e.exception)) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue