Add revision table to the SPL blob

It is becoming necessary to communicate the revision table to the SPL
blob so that it can decide what memory size to expect on the target.

The machine parameters structure allows only fixed size (32 bits)
elements. Let's add to the structure the offset of the array of bytes
representing the revision table.

The offset of the table is based on the address of the machine
parameter structure. The size of the table implicitly is defined as (3
^ NUM_GPIOS * 2) bytes, as each GPIO is a tertiary number, and for
each revision read from GPIOs the table includes two byte values:
revision and subrevision.

The size field of the variable size SPL blob header needs to be
updated to reflect the addition.

Instead of adding another elif case to the huge statement handling the
machine parameter names, this change introduces a dictionary for
routing processing based on the machine parameter name.

The fact that the blob could be resized by parameter processing
functions posed a challenge, as the blob is represented by a binary
string, and changing it in the function does not affect the caller.
The solution is to make the blob on of the ExynosBL2 class'
properties. This results in a larger change, but simplifies footprints
of many methods of this class.

For the purposes of checksum verification the BL1 expects the SPL size
to be divisible by 4, adding padding as necessary.

BRANCH=4482
BUG=chrome-os-partner:23024
TEST=manual
	. built and booted pit with the SPL machine param structure
	  including the new field, 't'. Noticed the increased size of
	  the SPL blob in the preserved output files directory.

        . analyzed header sizes before and after bundling:

	$ FEATURES=noclean emerge-peach_pit chromeos-u-boot chromeos-bootimage
	$ od -Ax -t x1 -v  /build/peach_pit/tmp/portage/sys-boot/chromeos-u-boot-9999/work/build/spl/smdk5420-spl.bin  '^000000 ' | head -1
	000000 80 40 00 00 35 66 17 00 00 00 00 00 00 00 00 00
	$ od -Ax -t x1 -v /build/peach_pit/firmware/image-peach-pit.bin |grep '002000 '
	002000 24 41 00 00 23 68 17 00 00 00 00 00 00 00 00 00

        Note that 0x4124 - 0x4080 = 164, which is 81 double bytes plus
        padding to keep the blob size divisible by 4

Change-Id: I4f0b005f53d4bc437780432fb59fb3c9088f4ec7
Signed-off-by: Vadim Bendebury <vbendeb@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/171772
Reviewed-by: Doug Anderson <dianders@chromium.org>
diff --git a/host/lib/exynos.py b/host/lib/exynos.py
index 82f19b4..7d52adb 100644
--- a/host/lib/exynos.py
+++ b/host/lib/exynos.py
@@ -60,6 +60,11 @@
             the caller
     _spl_type: an enum defined below, describing type (fixed of variable size)
             of the SPL being handled
+    _spl_data: a binary blob representing the SPL data being handled. It comes
+            as the value read from the u-boot makefile generated image and
+            goes as an enhanced image including the updated machine parameters
+            structure and possibly concatenated with the hash and revision
+            table.
   """
 
   VAR_SPL = 1
@@ -71,6 +76,7 @@
     self._out = output
     self._spl_type = None
     self.spl_source = 'straps'  # SPL boot according to board settings
+    self._spl_data = None  # SPL blob to configure
 
   def _BootingUsingEFS(self, fdt, use_efs_memory):
     """Check if we are booting using early-firmware-selection.
@@ -149,7 +155,27 @@
     addr = base + offset
     return 1, addr, size
 
-  def _UpdateParameters(self, fdt, spl_load_offset, spl_load_size, data, pos,
+  def _MpRevMap(self, _unused1, _unused2, fdt, pos):
+    """Append revision map to the SPL blob.
+
+    Read the revison map table from the device tree, concatenate it with the
+    SPL data blob and return the offset of the added table from the machine
+    parameter structure.
+
+    Args:
+      _unused1 - a single character string, machine parameter name
+      _unused2 - a 32 bit int read from the blob
+      fdt - the Fdt object representing the target device tree
+      pos - an int, offset of the machine parameter structure into data
+    """
+    # offset of the revision table from machine param table
+    offset = len(self._spl_data) - pos
+    rev_table =  fdt.GetIntList('/board-rev', 'google,board-rev-map')
+    extra = struct.pack('%dB' % len(rev_table), *rev_table)
+    self._spl_data += extra
+    return offset
+
+  def _UpdateParameters(self, fdt, spl_load_offset, spl_load_size, pos,
                         use_efs_memory, skip_sdram_init):
     """Update the parameters in a BL2 blob.
 
@@ -160,41 +186,69 @@
       fdt: Device tree containing the parameter values.
       spl_load_offset: Offset in boot media that SPL must start loading (bytes)
       spl_load_size: Size of U-Boot image that SPL must load
-      data: The BL2 data.
       pos: The position of the start of the parameter block.
       use_efs_memory: True to return the address in EFS memory (i.e. SRAM),
           False to use SDRAM
       skip_sdram_init: True to skip SDRAM initialization.
-
-    Returns:
-      The new contents of the parameter block, after updating.
     """
-    version, size = struct.unpack('<2L', data[pos + 4:pos + 12])
+    version, size = struct.unpack('<2L', self._spl_data[pos + 4:pos + 12])
     if version != 1:
       raise CmdError("Cannot update machine parameter block version '%d'" %
                      version)
-    if size < 0 or pos + size > len(data):
+    if size < 0 or pos + size > len(self._spl_data):
       raise CmdError('Machine parameter block size %d is invalid: '
                      'pos=%d, size=%d, space=%d, len=%d' %
-                     (size, pos, size, len(data) - pos, len(data)))
+                     (size, pos, size, len(self._spl_data)
+                      - pos, len(self._spl_data)))
 
     # Move past the header and read the parameter list, which is terminated
     # with \0.
     pos += 12
-    param_list = struct.unpack('<%ds' % (len(data) - pos), data[pos:])[0]
+    param_list = struct.unpack('<%ds' % (len(self._spl_data) - pos),
+                               self._spl_data[pos:])[0]
     param_len = param_list.find('\0')
     param_list = param_list[:param_len]
     pos += (param_len + 4) & ~3
 
+    #
+    # A dictionary of functions processing machine parameters. This is being
+    # introduced after more than 20 parameters have been already defined and
+    # are handled by the ugly if/elif/... construct below. It will be
+    # refactored eventually (one should hope), no new parameters' processing
+    # should be added there.
+    #
+    # The key of the dictionary is the parameter name, the value is a list.
+    # the first element of the list is the function to call to process the
+    # parameter, the rest of the elements are parameters to pass to the
+    # function.
+    #
+    # The first three parameters passed to the function are always prepended
+    # to those obtained from the list and are as follows:
+    #
+    # - the machine parameter name (one character string)
+    # - value read from the appropriate spot of the machine param structure,
+    #   as generated by the u-boot makefile, a - 32 bit int
+    # - fdt object representing the target FDT
+    #
+    # The function is expected to return a 32 bit value to plug into the
+    # machine parameters structure.
+    #
+    mp_router = {
+      't': [self._MpRevMap, pos]
+      }
+
+    # Use this to detect a missing value from the fdt.
+    not_given = 'not-given-invalid-value'
+
     # Work through the parameters one at a time, adding each value
     new_data = ''
     upto = 0
     for param in param_list:
-      value = struct.unpack('<1L', data[pos + upto:pos + upto + 4])[0]
+      value = struct.unpack('<1L', self._spl_data[pos + upto:pos + upto + 4])[0]
 
-      # Use this to detect a missing value from the fdt.
-      not_given = 'not-given-invalid-value'
-      if param == 'm':
+      if param in mp_router:
+        value = mp_router[param][0](param, value, fdt, *mp_router[param][1:])
+      elif param == 'm':
         mem_type = fdt.GetString('/dmc', 'mem-type', not_given)
         if mem_type == not_given:
           mem_type = 'ddr3'
@@ -373,50 +427,50 @@
       upto += 4
 
     # Put the data into our block.
-    data = data[:pos] + new_data + data[pos + len(new_data):]
-    return data
+    self._spl_data = self._spl_data[:pos] + new_data + self._spl_data[
+      pos + len(new_data):]
 
-  def _UpdateChecksum(self, data):
-    """Update the BL2 checksum.
+  def _UpdateChecksum(self):
+    """Update the BL2 size and checksum.
 
-    The checksum is a 4 byte sum of all the bytes in the image before the
-    last 4 bytes (which hold the checksum).
+    For the fixed size spl the checksum is a 4 byte sum of all the bytes in
+    the image before the last 4 bytes (which hold the checksum).
 
-    Args:
-      data: The BL2 data to update.
-
-    Returns:
-      The new contents of the BL2 data, after updating the checksum.
+    For the variable size SPL the first four bytes of the blob is the size of
+    the blob and the second four bytes is the sum of bytes in the rest of the
+    blob.
 
     Raises:
       CmdError if spl type is not set properly.
     """
 
     if self._spl_type == self.FIXED_SPL:
-      checksum = sum(ord(x) for x in data[:-4])
-      checksum_offset = len(data) - 4
+      checksum = sum(ord(x) for x in self._spl_data[:-4])
+      checksum_offset = len(self._spl_data) - 4
     elif self._spl_type == self.VAR_SPL:
-      checksum = sum(ord(x) for x in data[8:])
+      # Data size could have changed (the rev table could have been added).
+      if len(self._spl_data) % 4:
+        # Bl1 expects data size to be divisible by 4
+        self._spl_data += '\0' * (4 - len(self._spl_data) % 4)
+      self._spl_data = struct.pack('<L',
+                                   len(self._spl_data)) + self._spl_data[4:]
+      checksum = sum(ord(x) for x in self._spl_data[8:])
       checksum_offset = 4
     else:
       raise CmdError('SPL type not set')
 
-    return data[:checksum_offset]+ struct.pack(
-      '<L', checksum) + data[checksum_offset + 4:]
+    self._spl_data = self._spl_data[:checksum_offset] + struct.pack(
+      '<L', checksum) + self._spl_data[checksum_offset+4:]
 
-  def _UpdateHash(self, data, digest):
+  def _UpdateHash(self, digest):
     """Update the BL2 hash.
 
     The BL2 header may have a pointer to the hash block, but if not, then we
     add it (at the end of SPL).
 
     Args:
-      data: The data to update.
       digest: The hash digest to write.
 
-    Returns:
-      The new contents of the BL2 data, after updating the hash.
-
     Raises:
       CmdError if spl type is not variable size. We don't support this
           function with fixed-sized SPL.
@@ -425,24 +479,24 @@
       raise CmdError('Hash is only supported for variable-size SPL')
 
     # See if there is already a hash there.
-    hash_offset = struct.unpack('<L', data[8:12])[0]
+    hash_offset = struct.unpack('<L', self._spl_data[8:12])[0]
     if not hash_offset:
-      hash_offset = len(data)
+      hash_offset = len(self._spl_data)
     algo = 'sha256'.ljust(HASH_ALGO_LEN, '\x00')
     hash_block = algo + digest
     hash_block_len = len(hash_block) + HASH_HEADER_LEN
     hash_hdr = struct.pack('<4L', HASH_SIGNATURE, HASH_VERSION, hash_block_len,
                            HASH_FLAGS)
-    data = (data[:hash_offset] + hash_hdr + hash_block +
-            data[hash_offset + hash_block_len:])
+    self._spl_data = (self._spl_data[:hash_offset] + hash_hdr + hash_block +
+                      self._spl_data[hash_offset + hash_block_len:])
 
     # Update the size and hash_offset.
-    data = struct.pack('<LLL', len(data), 0, hash_offset) + data[12:]
+    self._spl_data = struct.pack('<LLL', len(self._spl_data), 0, hash_offset
+                                 ) + self._spl_data[12:]
     self._out.Info('  Added hash: %s' % ''.
                    join(['%02x' % ord(d) for d in digest]))
-    return data
 
-  def _VerifyBl2(self, data, loose_check):
+  def _VerifyBl2(self, loose_check):
     """Verify BL2 integrity.
 
     Fixed size and variable size SPL have different formats. Determine format,
@@ -450,13 +504,13 @@
     reference.
 
     Args:
-      data: The BL2 data to update.
       loose_check: a Boolean, if true - the variable size SPL blob could be
                    larger than the size value in the header
     Raises:
       CmdError if SPL blob is of unrecognizable format.
     """
 
+    data = self._spl_data  # Cache it to improve readability.
     # Variable size format is more sophisticated, check it first.
     try:
       size = struct.unpack('<I', data[:4])[0]
@@ -520,24 +574,26 @@
     """
     bl2 = os.path.join(self._tools.outdir, 'updated-spl%s.bin' % name)
     self._out.Info('Configuring BL2 %s' % bl2)
-    data = self._tools.ReadFile(orig_bl2)
-    self._VerifyBl2(data, loose_check)
+    self._spl_data = self._tools.ReadFile(orig_bl2)
+    self._VerifyBl2(loose_check)
 
     # Locate the parameter block
     marker = struct.pack('<L', 0xdeadbeef)
-    pos = data.rfind(marker)
+    pos = self._spl_data.rfind(marker)
     if not pos:
       raise CmdError("Could not find machine parameter block in '%s'" %
                      orig_bl2)
-    data = self._UpdateParameters(fdt, spl_load_offset, spl_load_size, data,
-                                  pos, use_efs_memory, skip_sdram_init)
+    self._UpdateParameters(fdt, spl_load_offset, spl_load_size,
+                           pos, use_efs_memory, skip_sdram_init)
     if digest:
-      data = self._UpdateHash(data, digest)
-    data = self._UpdateChecksum(data)
+      self._UpdateHash(digest)
+    self._UpdateChecksum()
 
-    self._tools.WriteFile(bl2, data)
+    self._tools.WriteFile(bl2, self._spl_data)
     return bl2
 
+# pylint: disable=E1101
+
   def MakeSpl(self, pack, fdt, blob, vanilla_bl2):
     """Create a configured SPL based on the supplied vanilla one.