image.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. # Copyright (c) 2016 Google, Inc
  2. # Written by Simon Glass <sjg@chromium.org>
  3. #
  4. # SPDX-License-Identifier: GPL-2.0+
  5. #
  6. # Class for an image, the output of binman
  7. #
  8. from collections import OrderedDict
  9. from operator import attrgetter
  10. import entry
  11. from entry import Entry
  12. import fdt_util
  13. import tools
  14. class Image:
  15. """A Image, representing an output from binman
  16. An image is comprised of a collection of entries each containing binary
  17. data. The image size must be large enough to hold all of this data.
  18. This class implements the various operations needed for images.
  19. Atrtributes:
  20. _node: Node object that contains the image definition in device tree
  21. _name: Image name
  22. _size: Image size in bytes, or None if not known yet
  23. _align_size: Image size alignment, or None
  24. _pad_before: Number of bytes before the first entry starts. This
  25. effectively changes the place where entry position 0 starts
  26. _pad_after: Number of bytes after the last entry ends. The last
  27. entry will finish on or before this boundary
  28. _pad_byte: Byte to use to pad the image where there is no entry
  29. _filename: Output filename for image
  30. _sort: True if entries should be sorted by position, False if they
  31. must be in-order in the device tree description
  32. _skip_at_start: Number of bytes before the first entry starts. These
  33. effecively adjust the starting position of entries. For example,
  34. if _pad_before is 16, then the first entry would start at 16.
  35. An entry with pos = 20 would in fact be written at position 4
  36. in the image file.
  37. _end_4gb: Indicates that the image ends at the 4GB boundary. This is
  38. used for x86 images, which want to use positions such that a
  39. memory address (like 0xff800000) is the first entry position.
  40. This causes _skip_at_start to be set to the starting memory
  41. address.
  42. _entries: OrderedDict() of entries
  43. """
  44. def __init__(self, name, node):
  45. self._node = node
  46. self._name = name
  47. self._size = None
  48. self._align_size = None
  49. self._pad_before = 0
  50. self._pad_after = 0
  51. self._pad_byte = 0
  52. self._filename = '%s.bin' % self._name
  53. self._sort = False
  54. self._skip_at_start = 0
  55. self._end_4gb = False
  56. self._entries = OrderedDict()
  57. self._ReadNode()
  58. self._ReadEntries()
  59. def _ReadNode(self):
  60. """Read properties from the image node"""
  61. self._size = fdt_util.GetInt(self._node, 'size')
  62. self._align_size = fdt_util.GetInt(self._node, 'align-size')
  63. if tools.NotPowerOfTwo(self._align_size):
  64. self._Raise("Alignment size %s must be a power of two" %
  65. self._align_size)
  66. self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
  67. self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
  68. self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
  69. filename = fdt_util.GetString(self._node, 'filename')
  70. if filename:
  71. self._filename = filename
  72. self._sort = fdt_util.GetBool(self._node, 'sort-by-pos')
  73. self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
  74. if self._end_4gb and not self._size:
  75. self._Raise("Image size must be provided when using end-at-4gb")
  76. if self._end_4gb:
  77. self._skip_at_start = 0x100000000 - self._size
  78. def CheckSize(self):
  79. """Check that the image contents does not exceed its size, etc."""
  80. contents_size = 0
  81. for entry in self._entries.values():
  82. contents_size = max(contents_size, entry.pos + entry.size)
  83. contents_size -= self._skip_at_start
  84. size = self._size
  85. if not size:
  86. size = self._pad_before + contents_size + self._pad_after
  87. size = tools.Align(size, self._align_size)
  88. if self._size and contents_size > self._size:
  89. self._Raise("contents size %#x (%d) exceeds image size %#x (%d)" %
  90. (contents_size, contents_size, self._size, self._size))
  91. if not self._size:
  92. self._size = size
  93. if self._size != tools.Align(self._size, self._align_size):
  94. self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
  95. (self._size, self._size, self._align_size, self._align_size))
  96. def _Raise(self, msg):
  97. """Raises an error for this image
  98. Args:
  99. msg: Error message to use in the raise string
  100. Raises:
  101. ValueError()
  102. """
  103. raise ValueError("Image '%s': %s" % (self._node.path, msg))
  104. def _ReadEntries(self):
  105. for node in self._node.subnodes:
  106. self._entries[node.name] = Entry.Create(self, node)
  107. def FindEntryType(self, etype):
  108. """Find an entry type in the image
  109. Args:
  110. etype: Entry type to find
  111. Returns:
  112. entry matching that type, or None if not found
  113. """
  114. for entry in self._entries.values():
  115. if entry.etype == etype:
  116. return entry
  117. return None
  118. def GetEntryContents(self):
  119. """Call ObtainContents() for each entry
  120. This calls each entry's ObtainContents() a few times until they all
  121. return True. We stop calling an entry's function once it returns
  122. True. This allows the contents of one entry to depend on another.
  123. After 3 rounds we give up since it's likely an error.
  124. """
  125. todo = self._entries.values()
  126. for passnum in range(3):
  127. next_todo = []
  128. for entry in todo:
  129. if not entry.ObtainContents():
  130. next_todo.append(entry)
  131. todo = next_todo
  132. if not todo:
  133. break
  134. def _SetEntryPosSize(self, name, pos, size):
  135. """Set the position and size of an entry
  136. Args:
  137. name: Entry name to update
  138. pos: New position
  139. size: New size
  140. """
  141. entry = self._entries.get(name)
  142. if not entry:
  143. self._Raise("Unable to set pos/size for unknown entry '%s'" % name)
  144. entry.SetPositionSize(self._skip_at_start + pos, size)
  145. def GetEntryPositions(self):
  146. """Handle entries that want to set the position/size of other entries
  147. This calls each entry's GetPositions() method. If it returns a list
  148. of entries to update, it updates them.
  149. """
  150. for entry in self._entries.values():
  151. pos_dict = entry.GetPositions()
  152. for name, info in pos_dict.iteritems():
  153. self._SetEntryPosSize(name, *info)
  154. def PackEntries(self):
  155. """Pack all entries into the image"""
  156. pos = self._skip_at_start
  157. for entry in self._entries.values():
  158. pos = entry.Pack(pos)
  159. def _SortEntries(self):
  160. """Sort entries by position"""
  161. entries = sorted(self._entries.values(), key=lambda entry: entry.pos)
  162. self._entries.clear()
  163. for entry in entries:
  164. self._entries[entry._node.name] = entry
  165. def CheckEntries(self):
  166. """Check that entries do not overlap or extend outside the image"""
  167. if self._sort:
  168. self._SortEntries()
  169. pos = 0
  170. prev_name = 'None'
  171. for entry in self._entries.values():
  172. if (entry.pos < self._skip_at_start or
  173. entry.pos >= self._skip_at_start + self._size):
  174. entry.Raise("Position %#x (%d) is outside the image starting "
  175. "at %#x (%d)" %
  176. (entry.pos, entry.pos, self._skip_at_start,
  177. self._skip_at_start))
  178. if entry.pos < pos:
  179. entry.Raise("Position %#x (%d) overlaps with previous entry '%s' "
  180. "ending at %#x (%d)" %
  181. (entry.pos, entry.pos, prev_name, pos, pos))
  182. pos = entry.pos + entry.size
  183. prev_name = entry.GetPath()
  184. def ProcessEntryContents(self):
  185. """Call the ProcessContents() method for each entry
  186. This is intended to adjust the contents as needed by the entry type.
  187. """
  188. for entry in self._entries.values():
  189. entry.ProcessContents()
  190. def BuildImage(self):
  191. """Write the image to a file"""
  192. fname = tools.GetOutputFilename(self._filename)
  193. with open(fname, 'wb') as fd:
  194. fd.write(chr(self._pad_byte) * self._size)
  195. for entry in self._entries.values():
  196. data = entry.GetData()
  197. fd.seek(self._pad_before + entry.pos - self._skip_at_start)
  198. fd.write(data)