futility: [deferredupdates] Defer setting firmware cookies

This is the firmware part of go/deferredupdates.

During autoupdates, it's required to defer firmware cookies (a.k.a. slot
switching) to not try the updated RW firmware. Instead it is deferred
for the values to be updated at a later time, when the actual update
should be applied.

Since there is not a clear communication method between
futility and postinstall+autoupdater, it'll be the case that STATUS will
be used as the IPC for postinstall+autoupdater to determine when to
update firmware cookies w/ a UUID.

```
// autoupdate
localhost ~ # chromeos-firmwareupdate --mode=autoupdate --wp=1

  Machine Model: gimble
  Write Protect: HW=0 SW=0
  Last Boot Version: RO=Google_Gimble.14498.0.0 ACT/B=Google_Gimble.14498.0.0
  Firmware Updater:  RO=Google_Gimble.14505.93.0    RW=Google_Gimble.14505.93.0

>> Starting firmware updater.
>> Target image: images/bios-gimble.ro-14505-93-0.rw-14505-93-0.bin (RO:Google_Gimble.14505.93.0, RW/A:Google_Gimble.14505.93.0, RW/B:Google_Gimble.14505.93.0).
>> Current system: <sys-flash> (RO:Google_Gimble.14498.0.0, RW/A:Google_Gimble.14505.93.0, RW/B:Google_Gimble.14498.0.0).
>> Write protection: 1 (enabled; HW=1, SW=1).
>> TRY-RW UPDATE: Updating RW_SECTION_A to try on reboot.
>> DONE: Firmware updater exits successfully.

fw_prev_result          = trying                         # [RO/str] Firmware result of previous boot
fw_prev_tried           = B                              # [RO/str] Firmware tried on previous boot (A or B)
fw_result               = success                        # [RW/str] Firmware result this boot
fw_tried                = B                              # [RO/str] Firmware tried this boot (A or B)
fw_try_count            = 10                             # [RW/int] Number of times to try fw_try_next
fw_try_next             = A                              # [RW/str] Firmware to try next (A or B)
fw_vboot2               = 1                              # [RO/int] 1 if firmware was selected by vboot2 or 0 otherwise
fwb_tries               = 10                             # [RW/int] Try firmware B count
fwid                    = Google_Gimble.14498.0.0        # [RO/str] Active firmware ID
fwupdate_tries          = 0                              # [RW/int] Times to try OS firmware update (inside kern_nv)
mainfw_act              = B                              # [RO/str] Active main firmware

// deferupdate HOLD
localhost ~ # chromeos-firmwareupdate --mode=deferupdate_hold --wp=1

  Machine Model: gimble
  Write Protect: HW=0 SW=0
  Last Boot Version: RO=Google_Gimble.14498.0.0 ACT/B=Google_Gimble.14498.0.0
  Firmware Updater:  RO=Google_Gimble.14505.93.0    RW=Google_Gimble.14505.93.0

>> Starting firmware updater.
>> Target image: images/bios-gimble.ro-14505-93-0.rw-14505-93-0.bin (RO:Google_Gimble.14505.93.0, RW/A:Google_Gimble.14505.93.0, RW/B:Google_Gimble.14505.93.0).
>> Current system: <sys-flash> (RO:Google_Gimble.14498.0.0, RW/A:Google_Gimble.14505.93.0, RW/B:Google_Gimble.14498.0.0).
>> Write protection: 1 (enabled; HW=1, SW=1).
>> TRY-RW UPDATE: Updating RW_SECTION_A to try on reboot.
>> DEFER UPDATE: Defer setting cookies for RW_SECTION_A.
>> DONE: Firmware updater exits successfully.

fw_prev_result          = trying                         # [RO/str] Firmware result of previous boot
fw_prev_tried           = B                              # [RO/str] Firmware tried on previous boot (A or B)
fw_result               = success                        # [RW/str] Firmware result this boot
fw_tried                = B                              # [RO/str] Firmware tried this boot (A or B)
fw_try_count            = 0                              # [RW/int] Number of times to try fw_try_next
fw_try_next             = B                              # [RW/str] Firmware to try next (A or B)
fw_vboot2               = 1                              # [RO/int] 1 if firmware was selected by vboot2 or 0 otherwise
fwb_tries               = 0                              # [RW/int] Try firmware B count
fwid                    = Google_Gimble.14498.0.0        # [RO/str] Active firmware ID
fwupdate_tries          = 0                              # [RW/int] Times to try OS firmware update (inside kern_nv)
mainfw_act              = B                              # [RO/str] Active main firmware

// deferupdate APPLY
localhost ~ # chromeos-firmwareupdate --mode=deferupdate_apply --wp=1

  Machine Model: gimble
  Write Protect: HW=0 SW=1
  Last Boot Version: RO=Google_Gimble.14498.0.0 ACT/B=Google_Gimble.14498.0.0
  Firmware Updater:  RO=Google_Gimble.14505.106.0    RW=Google_Gimble.14505.106.0

>> Starting firmware updater.
INFO: update_firmware: Apply defer updates, only setting cookies for the next boot slot.
>> DONE: Firmware updater exits successfully.

fw_prev_result          = trying                         # [RO/str] Firmware result of previous boot
fw_prev_tried           = B                              # [RO/str] Firmware tried on previous boot (A or B)
fw_result               = success                        # [RW/str] Firmware result this boot
fw_tried                = B                              # [RO/str] Firmware tried this boot (A or B)
fw_try_count            = 10                             # [RW/int] Number of times to try fw_try_next
fw_try_next             = A                              # [RW/str] Firmware to try next (A or B)
fw_vboot2               = 1                              # [RO/int] 1 if firmware was selected by vboot2 or 0 otherwise
fwb_tries               = 10                             # [RW/int] Try firmware B count
fwid                    = Google_Gimble.14498.0.0        # [RO/str] Active firmware ID
fwupdate_tries          = 0                              # [RW/int] Times to try OS firmware update (inside kern_nv)
mainfw_act              = B                              # [RO/str] Active main firmware
```

BUG=b:232304971
TEST=chromeos-firmwareupdate w/ comment above
BRANCH=None

Signed-off-by: Jae Hoon Kim <kimjae@chromium.org>
Change-Id: Idcfc5864a2cfc2b46a8b936bbab61e3da7c62596
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/vboot_reference/+/3661357
Commit-Queue: Yu-Ping Wu <yupingso@chromium.org>
Reviewed-by: Hung-Te Lin <hungte@chromium.org>
Reviewed-by: Yu-Ping Wu <yupingso@chromium.org>
diff --git a/futility/updater.c b/futility/updater.c
index dcbc445..c28c5ba 100644
--- a/futility/updater.c
+++ b/futility/updater.c
@@ -20,6 +20,13 @@
 static const char ROOTKEY_HASH_DEV[] =
 		"b11d74edd286c144e1135b49e7f0bc20cf041f10";
 
+enum try_update_type {
+	TRY_UPDATE_OFF = 0,
+	TRY_UPDATE_AUTO,
+	TRY_UPDATE_DEFERRED_HOLD,
+	TRY_UPDATE_DEFERRED_APPLY,
+};
+
 enum target_type {
 	TARGET_SELF,
 	TARGET_UPDATE,
@@ -951,7 +958,7 @@
 		struct firmware_image *image_to,
 		int wp_enabled)
 {
-	const char *target;
+	const char *target, *self_target;
 	int has_update = 1;
 	int is_vboot2 = get_system_property(SYS_PROP_FW_VBOOT2, cfg);
 
@@ -967,7 +974,7 @@
 		return UPDATE_ERR_TPM_ROLLBACK;
 
 	VB2_DEBUG("Firmware %s vboot2.\n", is_vboot2 ?  "is" : "is NOT");
-	target = decide_rw_target(cfg, TARGET_SELF, is_vboot2);
+	self_target = target = decide_rw_target(cfg, TARGET_SELF, is_vboot2);
 	if (target == NULL) {
 		ERROR("TRY-RW update needs system to boot in RW firmware.\n");
 		return UPDATE_ERR_TARGET;
@@ -979,7 +986,7 @@
 		      target, image_to->file_name);
 		return UPDATE_ERR_INVALID_IMAGE;
 	}
-	if (!cfg->force_update)
+	if (!(cfg->force_update || cfg->try_update == TRY_UPDATE_DEFERRED_HOLD))
 		has_update = section_needs_update(image_from, image_to, target);
 
 	if (has_update) {
@@ -989,6 +996,24 @@
 
 		if (write_firmware(cfg, image_to, target))
 			return UPDATE_ERR_WRITE_FIRMWARE;
+
+		/*
+		 * If the firmware update requested is part of a deferred update
+		 * HOLD action, the autoupdater/postinstall will later call
+		 * defer update APPLY action to set the correct cookies. So here
+		 * it is valid to keep the self slot as the active firmware even
+		 * though the target slot is always updated (whether the current
+		 * active firmware is the same version or not).
+		 */
+		if (cfg->try_update == TRY_UPDATE_DEFERRED_HOLD) {
+			STATUS(
+			    "DEFERRED UPDATE: Defer setting cookies for %s\n",
+			    target);
+			target = self_target;
+			has_update = 0;
+		}
+	} else {
+		STATUS("NO RW UPDATE: No update for RW firmware.\n");
 	}
 
 	/* Always set right cookies for next boot. */
@@ -1002,9 +1027,6 @@
 		write_firmware(cfg, image_to, FMAP_RW_LEGACY);
 	}
 
-	if (!has_update)
-		STATUS("NO UPDATE: No need to update.\n");
-
 	return UPDATE_ERR_DONE;
 }
 
@@ -1149,6 +1171,23 @@
 	int wp_enabled, done = 0;
 	enum updater_error_codes r = UPDATE_ERR_UNKNOWN;
 
+	/*
+	 * For deferred update APPLY action, the only requirement is to set the
+	 * correct cookies to the update target slot.
+	 */
+	if (cfg->try_update == TRY_UPDATE_DEFERRED_APPLY) {
+		INFO("Apply deferred updates, only setting cookies for the "
+		     "next boot slot.\n");
+		int vboot2 = get_system_property(SYS_PROP_FW_VBOOT2, cfg);
+		if (set_try_cookies(
+			cfg,
+			decide_rw_target(cfg, TARGET_UPDATE, vboot2),
+			/*has_update=*/1,
+			vboot2))
+			return UPDATE_ERR_SET_COOKIES;
+		return UPDATE_ERR_DONE;
+	}
+
 	struct firmware_image *image_from = &cfg->image_current,
 			      *image_to = &cfg->image;
 	if (!image_to->data)
@@ -1473,12 +1512,16 @@
 
 	/* Setup update mode. */
 	if (arg->try_update)
-		cfg->try_update = 1;
+		cfg->try_update = TRY_UPDATE_AUTO;
 	if (arg->mode) {
 		if (strcmp(arg->mode, "autoupdate") == 0) {
-			cfg->try_update = 1;
+			cfg->try_update = TRY_UPDATE_AUTO;
+		} else if (strcmp(arg->mode, "deferupdate_hold") == 0) {
+			cfg->try_update = TRY_UPDATE_DEFERRED_HOLD;
+		} else if (strcmp(arg->mode, "deferupdate_apply") == 0) {
+			cfg->try_update = TRY_UPDATE_DEFERRED_APPLY;
 		} else if (strcmp(arg->mode, "recovery") == 0) {
-			cfg->try_update = 0;
+			cfg->try_update = TRY_UPDATE_OFF;
 		} else if (strcmp(arg->mode, "legacy") == 0) {
 			cfg->legacy_update = 1;
 		} else if (strcmp(arg->mode, "factory") == 0 ||
@@ -1494,7 +1537,7 @@
 	if (cfg->factory_update) {
 		/* factory_update must be processed after arg->mode. */
 		check_wp_disabled = 1;
-		cfg->try_update = 0;
+		cfg->try_update = TRY_UPDATE_OFF;
 	}
 	cfg->gbb_flags = arg->gbb_flags;
 	cfg->override_gbb_flags = arg->override_gbb_flags;