firmware: preserve VPD in factory / incompatibe update modes (merge from ToT to R12)

Firmware updating should always preserve the content in RO/RW VPD area.

BUG=chrome-os-partner:3540
TEST=vpd -i RO_VPD -s ro=1; vpd -i RW_VPD -s rw=2;
     vpd -l # put some value
     flashrom -r bios_old.bin; # for backup
     ./updater --mode=factory; vpd -l # see values not changed
     flashrom -r bios_new.bin; # new firmware
     (mkdir old; cd old; dump_fmap -x ../bios_old.bin)
     (mkdir new; cd new; dump_fmap -x ../bios_new.bin)
     for X in old/*; do cmp "$X" "new/$(basnema $X)" || echo "diff"; done
     # seeing only RO_VPD and RW_VPD are different

Change-Id: I4aeb6ba3096700b33d1734fcd15e784555ef3fcf
Cherry-Picked: http://gerrit.chromium.org/gerrit/#change,277
Reviewed-on: http://gerrit.chromium.org/gerrit/489
Reviewed-by: Hung-Te Lin <hungte@chromium.org>
Tested-by: Hung-Te Lin <hungte@chromium.org>
diff --git a/pack_dist/crosutil.sh b/pack_dist/crosutil.sh
index 870f5d0..2e8864d 100644
--- a/pack_dist/crosutil.sh
+++ b/pack_dist/crosutil.sh
@@ -17,6 +17,12 @@
   [ -n "$hash1" ] && [ "$hash1" = "$hash2" ]
 }
 
+# Gets file size.
+cros_get_file_size() {
+  [ -e "$1" ] || err_die "cros_get_file_size: invalid file: $1"
+  stat -c "%s" "$1" 2>/dev/null
+}
+
 # Gets a Chrome OS system property (must exist).
 cros_get_prop() {
   crossystem "$@" || err_die "cannot get crossystem property: $@"
diff --git a/pack_dist/updater.sh b/pack_dist/updater.sh
index fd21b76..0bb6d31 100644
--- a/pack_dist/updater.sh
+++ b/pack_dist/updater.sh
@@ -171,10 +171,34 @@
 # ----------------------------------------------------------------------------
 # Helper functions
 
+# Preserve VPD data by merging sections from current system into target
+# firmware. Note this will change $IMAGE_MAIN so any processing to the file (ex,
+# prepare_image) must be invoked AFTER this call.
 preserve_vpd() {
-  # TODO(hungte) pull VPD from system live firmware and dupe into $IMAGE_MAIN.
-  # NOTE: live may not have FMAP if it's not H2C, so backup+restore won't work.
-  alert "Warning: VPD preserving is not implemented yet."
+  # Preserve VPD when updating an existing system (maya be legacy firmware or
+  # ChromeOS firmware). The system may not have FMAP, so we need to use
+  # emulation mode otherwise reading sections may fail.
+  if [ "${FLAGS_update_main}" = ${FLAGS_FALSE} ]; then
+    debug_msg "not updating main firmware, skip preserving VPD..."
+    return $FLAGS_TRUE
+  fi
+  debug_msg "preserving VPD..."
+  local temp_file="_vpd_temp.bin"
+  local vpd_list="-i RO_VPD -i RW_VPD"
+  silent_invoke "flashrom $TARGET_OPT_MAIN -r $temp_file" ||
+    die "Failed to read current main firmware."
+  local size_current="$(cros_get_file_size "$temp_file")"
+  local size_target="$(cros_get_file_size "$IMAGE_MAIN")"
+  if [ -z "$size_current" ] ||
+     [ "$size_current" = "0" ] ||
+     [ "$size_current" != "$size_target" ]; then
+     err_die "Failed to read preserve VPD content. Abort."
+  fi
+
+  local param="dummy:emulate=VARIABLE_SIZE,image=$IMAGE_MAIN,size=$size_current"
+  silent_invoke "flashrom -p $param $vpd_list -w $temp_file" ||
+    die "Failed to update VPD from existing system."
+  debug_msg "preserve_vpd: $IMAGE_MAIN updated."
 }
 
 preserve_hwid() {
@@ -572,8 +596,8 @@
     err_die "You need to first disable hardware write protection switch."
   fi
   if [ "${FLAGS_update_main}" = "${FLAGS_TRUE}" ]; then
-    prepare_main_image
     preserve_vpd
+    prepare_main_image
     # Preserve BMPFV
     obtain_bmpfv
     preserve_bmpfv