cros_write_firmware: Extract FDT from image automatically

It is a pain to have to specify the board and FDT when writing an image.
Extract the FDT from the image instead, while still allowing an override
if required.

BUG=chromium:245311
BRANCH=none
TEST=manual
Using any image build from ToT, this command should flash it over USB:

$ cros_write_firmware /path/to/image.bin -F spi

For x86, this command should write it to em100:

$ cros_write_firmware /path/to/image.bin

Tested on pit and link.

Change-Id: I6fedec0ee6a6f877f72abe963b4b92bff82345eb
Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/66922
Reviewed-by: Julius Werner <jwerner@chromium.org>
Reviewed-by: Randall Spangler <rspangler@chromium.org>
diff --git a/host/cros_write_firmware b/host/cros_write_firmware
index ab832b8..48e65f7 100755
--- a/host/cros_write_firmware
+++ b/host/cros_write_firmware
@@ -55,6 +55,19 @@
   bundle = Bundle(tools, output)
   bundle.SetFiles(board=options.board, bct=options.bct,
       exynos_bl1=options.exynos_bl1, exynos_bl2=options.exynos_bl2)
+
+  # Get the FDT from the image if possible.
+  if not options.fdt:
+    fdt_fname = tools.GetOutputFilename('fdt-from-image.dtb')
+    fdt = Fdt(tools, fdt_fname)
+    fdt.ExtractFromImage(options.image)
+    options.fdt = fdt_fname
+    if not options.board:
+      bundle._board = fdt.GetString('/chromeos-config', 'board', 'none')
+      if bundle._board == 'none':
+        raise ValueError('Cannot obtain board type from FDTMAP')
+      output.Notice("Using board from fdtmap: '%s'" % bundle._board)
+
   if options.use_defaults:
     bundle.CheckOptions()
   bundle.spl_source = options.spl_source
@@ -125,8 +138,6 @@
     del args[1]
   if not options.image:
     parser.error('Please use --image to specify a firmware image to flash');
-  if not options.board:
-    parser.error('Please use --board to specify the board type to flash');
   if len(args) > 1:
     parser.error("Unrecognized arguments '%s'" % ' '.join(args[1:]))
 
diff --git a/host/lib/bundle_firmware.py b/host/lib/bundle_firmware.py
index a8c697f..eb4a314 100644
--- a/host/lib/bundle_firmware.py
+++ b/host/lib/bundle_firmware.py
@@ -1096,6 +1096,9 @@
       self._out.Warning('Missing properties in /config, using defaults')
       fdt.InsertNodes([i for i in default_flashmap if i['path'] == '/config'])
 
+    # Remember our board type.
+    fdt.PutString('/chromeos-config', 'board', self._board)
+
     self.fdt = fdt
     return fdt
 
diff --git a/host/lib/fdt.py b/host/lib/fdt.py
index c59487d..e2964a2 100644
--- a/host/lib/fdt.py
+++ b/host/lib/fdt.py
@@ -6,11 +6,13 @@
 
 """This library provides basic access to an fdt blob."""
 
+import binascii
 import doctest
 import optparse
 import os
 import re
 import shutil
+import struct
 import sys
 
 import cros_output
@@ -32,6 +34,26 @@
     _, ext = os.path.splitext(fname)
     self._is_compiled = ext == '.dtb'
 
+  def ExtractFromImage(self, fname):
+    """Extract an FDT from a Chrome OS firmware image (FDTMAP).
+
+    Args:
+      fname: Filename of image
+
+    Raises:
+      ValueError if no fdtmap is found in the image, or its crc32 is incorrect.
+    """
+    data = self.tools.ReadFile(fname)
+    pos = data.find('__FDTM__')
+    if pos == -1:
+      raise ValueError("No fdtmap found in image '%s'" % fname)
+    size, crc32 = struct.unpack('<LL', data[pos + 8:pos + 16])
+    fdt_data = data[pos + 16:pos + 16 + size]
+    check_crc32 = binascii.crc32(fdt_data) & 0xffffffff
+    if check_crc32 != crc32:
+      raise ValueError('Invalid CRC for FDTMAP')
+    self.tools.WriteFile(self.fname, fdt_data)
+
   def GetProp(self, node, prop, default=None, typespec=None):
     """Get a property from a device tree.