sign_gsc_firmware: add functions to determine ihex module base address

With the advent of D2 memory layout scheme it became impossible to
hardcode the base address of various components of the D2 firmware
image. Luckily, the components are represented as binary blobs in
Intel ihex format, which allows to retrieve the base address of the
component from the ihex records.

The address is composed of two elements: the segment base supplied in
the record type 02 or 04, and the record offset into the segment,
supplied in the data record of type 0.

The segment address is expressed as a 16 bit value, the actual value
shifted right either 4 bits (in case of record type 02) or 16 bits (in
case of record type 04). The data record offset is also a 16 bit
value.

The base address of the blob is calculated as

<segment address> + <first data record offset>

and is available from the first two records in the ihex module.

Detailed information of ihex file format can be found in
https://en.wikipedia.org/wiki/Intel_HEX .

BRANCH=none
BUG=b:173049030
TEST=with the next patch in the stack applied was able to successfully
     build a multicomponent ti50 image.

Change-Id: I135c2f9960f1f218532c82bafd7acbe362414fc9
Signed-off-by: Vadim Bendebury <vbendeb@google.com>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/vboot_reference/+/2570008
Tested-by: Vadim Bendebury <vbendeb@chromium.org>
Reviewed-by: George Engelbrecht <engeg@google.com>
Commit-Queue: Vadim Bendebury <vbendeb@chromium.org>
diff --git a/scripts/image_signing/sign_gsc_firmware.sh b/scripts/image_signing/sign_gsc_firmware.sh
index 768844a..a464b9b 100755
--- a/scripts/image_signing/sign_gsc_firmware.sh
+++ b/scripts/image_signing/sign_gsc_firmware.sh
@@ -36,6 +36,92 @@
            print (struct.unpack('i', d)[0])"
 }
 
+# Functions allowing to determine the base address of a binary blob in ihex
+# format. Invoked in a subprocess through () to be able to use stdout as the
+# return values.
+
+# In ihex format binary data is represented as a set of records. Each record
+# is a text string of hex values in ASCII. All records start with a header
+# which determines the record contents.
+#
+# The most common record type is the data record, its header includes the 16
+# bit address of where the record data will have to be placed in the physical
+# address space. Naturally 16 bits is not enough as of last thirty years, some
+# special types of record are used to specify the segment base of there the
+# 16 bit address is used as the offset.
+#
+# The segment base is still represented as a 16 bit value, depending on the
+# record type the base is shifted right ether 4 (record type 02) or 16 (record
+# type 04) bits.
+#
+# The first two records of the ihex binary blob are a segment record and a
+# data record. Combining the segment value from the first record and the
+# address value from the second record one can determine the base address
+# where the blob is supposed to be placed.
+#
+# See https://en.wikipedia.org/wiki/Intel_HEX for further details.
+parse_segment() {
+  local string="$1"
+
+  if [[ "${string}" =~ ^:020000 && "${#string}" -eq 15 ]]; then
+    local type="${string:7:2}"
+    local value="0x${string:9:4}"
+    local segment
+
+    case "${type}" in
+      (02)
+        segment=$(( value << 4 ))
+        ;;
+      (04)
+        segment=$(( value << 16 ))
+        ;;
+      (*)
+        error "unknown segment record type ${type}"
+        ;;
+    esac
+    printf "0x%x" "${segment}"
+  else
+    error "unexpected segment record: ${string}"
+  fi
+}
+
+# The second record in the ihex binary blob is mapped to the lowest 16 bit
+# address in the segment.
+parse_data() {
+  local string="$1"
+
+  if [[ "${string}" =~ ^:10 && "${#string}" -eq 43 ]]; then
+    echo "0x${string:3:4}"
+  else
+    error "unexpected data record: ${string}"
+  fi
+}
+
+# Given an ihex binary blob determine its base address as a sum of the segment
+# address and the offset of the first record into the segment.
+get_hex_base() {
+  local hexf="$1"
+  local strings
+  local segment
+  local base_offset
+
+  # Some ihex blobs include <cr><lf>, drop <cr> to allow for fixed size check.
+  mapfile -t strings < <(head -2 "${hexf}" | sed 's/\x0d//')
+
+  if [[ ${#strings[@]} != 2 ]]; then
+    error "input file ${hexf} too short"
+    return
+  fi
+  segment="$(parse_segment "${strings[0]}")"
+  base_offset="$(parse_data "${strings[1]}")"
+
+  if [[ -n "${segment}" && -n "${base_offset}" ]]; then
+    printf "%d\n" $(( segment + base_offset ))
+  else
+    error "${hexf} does not seem to be a valid ihex module."
+  fi
+}
+
 # This function accepts one argument, the name of the GSC manifest file which
 # needs to be verified and in certain cases altered.
 #