cros_bundle_firmware: Support creation of IFD images

If we have an IFD (Intel Firmware Descriptor) region in the map, then we
create a skeleton image, and add the rest of the image to it. This is a
bit strange, so here is a more detailed explation:

As part of the PackFirmware process, we create, say, an 8MB image with
all the regions in it. Let's say the first 2MB are the Intel ME (Management
Engine) code, and the section type of this first 2MB section is "ifd".

When we see this ifd region, we remember it for later. Later, when we have
the full image, we pull off the first 2MB and use ifdtool to add the
remaining 6MB of the image to a provided 8MB skeleton image. We will end
up with an 8MB image, but now it is one that contains an IFD and has been
built by ifdtool. It is suitable for loading into a machine with ME.

BUG=chrome-os-partner:7969
TEST=manual
Modify link flash map to include an ifd section type for the ME section.
Take out the mangling of the image in chromeos-bootimage
$ cros_bundle_firmware -b link -d <device tree file>
Boot the image on link, see that it works ok

Change-Id: I402ecbf407226ff4f249aa1e52db4e94692daa19
Reviewed-on: https://gerrit.chromium.org/gerrit/25406
Reviewed-by: Simon Glass <sjg@chromium.org>
Tested-by: Simon Glass <sjg@chromium.org>
Commit-Ready: Simon Glass <sjg@chromium.org>
diff --git a/host/lib/pack_firmware.py b/host/lib/pack_firmware.py
index cacf90b..f7c17e0 100644
--- a/host/lib/pack_firmware.py
+++ b/host/lib/pack_firmware.py
@@ -7,6 +7,7 @@
 import optparse
 import os
 import re
+import shutil
 import struct
 import subprocess
 import sys
@@ -251,6 +252,53 @@
     return self.value
 
 
+class EntryIfd(EntryFmapArea):
+  """This entry marks the use of an Intel Firmware Descriptor.
+
+  The entry itself covers the 'Intel' part of the firmware, containing the
+  firmware descriptor and management engine. When this entry appears in a
+  flash map, we strip it off the image, and use ifdtool to place the rest
+  of the image into a provided skeleton file (which contains the management
+  engine and a skeleton firmware descriptor).
+  """
+  def __init__(self, props):
+    super(EntryIfd, self).__init__(props)
+
+  def ProduceFinalImage(self, tools, out, tmpdir, image_fname):
+    """Produce the final image for an Intel ME system
+
+    Some Intel systems require that an image contains the Management Engine
+    firmware, and also a firmware descriptor.
+
+    This function takes the existing image, removes the front part of it,
+    and replaces it with these required pieces using ifdtool.
+
+    Args:
+      tools: Tools object to use to run tools.
+      out: Output object to send output to
+      tmpdir: Temporary directory to use to create required files.
+      image_fname: Output image filename
+    """
+    out.Progress('Setting up Intel ME')
+    data = tools.ReadFile(image_fname)
+
+    # We can assume that the ifd section is at the start of the image.
+    if self.offset != 0:
+      raise ConfigError('IFD section must be at offset 0 in the image')
+    data = data[self.size:]
+    input_fname = os.path.join(tmpdir, 'ifd-input.bin')
+    tools.WriteFile(input_fname, data)
+    ifd_output = os.path.join(tmpdir, 'image.ifd')
+
+    # This works by modifying a skeleton file.
+    shutil.copyfile(tools.Filename(self.pack.props['skeleton']), ifd_output)
+    args = ['-i', 'BIOS:%s' % input_fname, ifd_output]
+    tools.Run('ifdtool', args)
+
+    # ifdtool puts the output in a file with '.new' tacked on the end.
+    shutil.move(ifd_output + '.new', image_fname)
+    tools.OutputSize('IFD image', image_fname)
+
 class EntryBlob(EntryFmapArea):
   """This entry contains a binary blob.
 
@@ -415,6 +463,8 @@
       entry = EntryBlobString(props)
     elif ftype == 'fmap':
       entry = EntryFmap(props)
+    elif ftype == 'ifd':
+      entry = EntryIfd(props)
     else:
       raise ValueError('%s: unknown entry type' % ftype)
 
@@ -631,6 +681,7 @@
         image.write('\0' * self.image_size)
 
       # Pack all the entriess.
+      ifd = None
       for entry in self.entries:
         if not entry.required:
           continue
@@ -639,6 +690,8 @@
         if type(entry) == EntryFmap:
           entry.SetEntries(base=0, image_size=self.image_size,
               entries=self.entries)
+        elif type(entry) == EntryIfd:
+          ifd = entry
 
         try:
           # First run any required tools.
@@ -663,6 +716,10 @@
         except PackError as err:
           raise ValueError('Packing error: %s' % err)
 
+    # If the image contain an IFD section, process it
+    if ifd:
+      ifd.ProduceFinalImage(self.tools, self._out, self.tmpdir, output_path)
+
   def _OutEntry(self, status, offset, size, name):
     """Display a flash map entry.