summaryrefslogtreecommitdiff
path: root/futility/updater.c
diff options
context:
space:
mode:
Diffstat (limited to 'futility/updater.c')
-rw-r--r--futility/updater.c195
1 files changed, 154 insertions, 41 deletions
diff --git a/futility/updater.c b/futility/updater.c
index 18fc339f..a360f2c8 100644
--- a/futility/updater.c
+++ b/futility/updater.c
@@ -893,16 +893,12 @@ static int write_firmware(struct updater_config *cfg,
}
/*
- * Write a section from given firmware image to system firmware if possible.
- * If section_name is NULL, write whole image. If the image has no data or if
- * the section does not exist, ignore and return success.
- * Returns 0 if success, non-zero if error.
+ * Returns True if we should start the update process for given image.
*/
-static int write_optional_firmware(struct updater_config *cfg,
- const struct firmware_image *image,
- const char *section_name,
- int check_programmer_wp,
- int is_host)
+static int has_valid_update(struct updater_config *cfg,
+ const struct firmware_image *image,
+ const char *section_name,
+ int is_host)
{
if (!image->data) {
VB2_DEBUG("No data in <%s> image.\n", image->programmer);
@@ -921,7 +917,23 @@ static int write_optional_firmware(struct updater_config *cfg,
image->file_name, image->programmer, image->size);
return 0;
}
+ return 1;
+}
+/*
+ * Write a section from given firmware image to system firmware if possible.
+ * If section_name is NULL, write whole image. If the image has no data or if
+ * the section does not exist, ignore and return success.
+ * Returns 0 if success, non-zero if error.
+ */
+static int write_optional_firmware(struct updater_config *cfg,
+ const struct firmware_image *image,
+ const char *section_name,
+ int check_programmer_wp,
+ int is_host)
+{
+ if (!has_valid_update(cfg, image, section_name, is_host))
+ return 0;
/*
* EC & PD may have different WP settings and we want to write
* only if it is OK.
@@ -1395,6 +1407,23 @@ static int cbfs_file_exists(const char *image_file,
return !r;
}
+static int cbfs_extract_file(const char *image_file,
+ const char *section_name,
+ const char *cbfs_entry_name,
+ const char *output_name)
+{
+ char *cmd;
+ int r;
+
+ ASPRINTF(&cmd,
+ "cbfstool '%s' extract -r %s -n '%s' -f '%s' "
+ "2>/dev/null", image_file, section_name, cbfs_entry_name,
+ output_name);
+ r = system(cmd);
+ free(cmd);
+ return r;
+}
+
/*
* Returns non-zero if the RW_LEGACY needs to be updated, otherwise 0.
*/
@@ -1488,44 +1517,129 @@ static int check_compatible_tpm_keys(struct updater_config *cfg,
}
/*
- * Returns the name of EC section to update in recovery (whole update).
+ * Returns True if the system has EC software sync enabled.
+ */
+static int is_ec_software_sync_enabled(struct updater_config *cfg)
+{
+ const struct vb2_gbb_header *gbb;
+
+ /* Check if current system has disabled software sync or no support. */
+ if (!(VbGetSystemPropertyInt("vdat_flags") & VBSD_EC_SOFTWARE_SYNC)) {
+ INFO("EC Software Sync is not available.\n");
+ return 0;
+ }
+
+ /* Check if the system has been updated to disable software sync. */
+ gbb = find_gbb(&cfg->image);
+ if (!gbb) {
+ WARN("Invalid AP firmware image.\n");
+ return 0;
+ }
+ if (gbb->flags & VB2_GBB_FLAG_DISABLE_EC_SOFTWARE_SYNC) {
+ INFO("EC Software Sync will be disabled in next boot.\n");
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * Schedules an EC RO software sync (in next boot) if applicable.
+ */
+static int ec_ro_software_sync(struct updater_config *cfg)
+{
+ const char *tmp_path = updater_create_temp_file(cfg);
+ const char *ec_ro_path = updater_create_temp_file(cfg);
+ uint8_t *ec_ro_data;
+ uint32_t ec_ro_len;
+ int is_same_ec_ro;
+ struct firmware_section ec_ro_sec;
+
+ if (!tmp_path || !ec_ro_path ||
+ vb2_write_file(tmp_path, cfg->image.data, cfg->image.size)) {
+ ERROR("Failed to create temporary file for image contents.\n");
+ return 1;
+ }
+ find_firmware_section(&ec_ro_sec, &cfg->ec_image, "EC_RO");
+ if (!ec_ro_sec.data || !ec_ro_sec.size) {
+ ERROR("EC image has invalid section '%s'.\n", "EC_RO");
+ return 1;
+ }
+ if (cbfs_extract_file(tmp_path, FMAP_RO_SECTION, "ec_ro", ec_ro_path) ||
+ !cbfs_file_exists(tmp_path, FMAP_RO_SECTION, "ec_ro.hash")) {
+ INFO("No valid EC RO for software sync in AP firmware.\n");
+ return 1;
+ }
+ if (vb2_read_file(ec_ro_path, &ec_ro_data, &ec_ro_len) != VB2_SUCCESS) {
+ ERROR("Failed to read EC RO.\n");
+ return 1;
+ }
+
+ is_same_ec_ro = (ec_ro_len <= ec_ro_sec.size &&
+ memcmp(ec_ro_sec.data, ec_ro_data, ec_ro_len) == 0);
+ free(ec_ro_data);
+
+ if (!is_same_ec_ro) {
+ /* TODO(hungte) If change AP RO is not a problem (hash will be
+ * different, which may be a problem to factory and HWID), or if
+ * we can be be sure this is for developers, extract EC RO and
+ * update AP RO CBFS to trigger EC RO sync with new EC.
+ */
+ ERROR("The EC RO contents specified from AP (--image) and EC "
+ "(--ec_image) firmware images are different, cannot "
+ "update by EC RO software sync.\n");
+ return 1;
+ }
+ VbSetSystemPropertyInt("try_ro_sync", 1);
+ return 0;
+}
+
+/*
+ * Returns True if EC is running in RW.
+ */
+static int is_ec_in_rw(void)
+{
+ char buf[VB_MAX_STRING_PROPERTY];
+ return (VbGetSystemPropertyString("ecfw_act", buf, sizeof(buf)) &&
+ strcasecmp(buf, "RW") == 0);
+}
+
+/*
+ * Update EC (RO+RW) in most reliable way.
*
* Some EC will reset TCPC when doing sysjump, and will make rootfs unavailable
- * if the system was boot from USB. There is no solution if EC already in RO
- * (recovery). But for developers who run updater manually in developer mode
- * (Ctrl-U, EC in RW) and have software sync enabled, we may update only EC RO
- * (skip RW).
+ * if the system was boot from USB, or other unexpected issues even if the
+ * system was boot from internal disk. To prevent that, try to partial update
+ * only RO and expect EC software sync to update RW later, or perform EC RO
+ * software sync.
+ *
+ * Returns 0 if success, non-zero if error.
*/
-static const char * get_ec_section_to_recover(struct updater_config *cfg)
+static int update_ec_firmware(struct updater_config *cfg)
{
- const char * const ec_ro = "EC_RO";
- char buf[VB_MAX_STRING_PROPERTY];
+ const char *ec_ro = "EC_RO";
+ struct firmware_image *ec_image = &cfg->ec_image;
- /* For devices without Chrome OS EC image, update all. */
- if (!cfg->ec_image.data ||
- !firmware_section_exists(&cfg->ec_image, ec_ro))
- return NULL;
+ /* TODO(hungte) Check if we have EC RO in AP image without --ec_image */
+ if (!has_valid_update(cfg, ec_image, NULL, 0))
+ return 0;
- /* For devices where EC is not running in RW, try to update all. */
- if (!VbGetSystemPropertyString("ecfw_act", buf, sizeof(buf)) ||
- strcasecmp(buf, "RW") != 0) {
- WARN("EC is not running in RW so updating RO may cause sysjump "
- "and you may see 'Input/output error' after that. "
- "See http://crbug.com/782427#c4 for more information.\n");
- return NULL;
+ if (!firmware_section_exists(ec_image, ec_ro)) {
+ INFO("EC image does not have section '%s'.\n", ec_ro);
+ } else if (!is_ec_software_sync_enabled(cfg)) {
+ /* Message already printed. */
+ } else if (is_ec_in_rw()) {
+ WARN("EC Software Sync detected, will only update EC RO. "
+ "The contents in EC RW will be updated after reboot.\n");
+ return write_optional_firmware(cfg, ec_image, ec_ro, 1, 0);
+ } else if (ec_ro_software_sync(cfg) == 0) {
+ INFO("EC RO and RW should be updated after reboot.\n");
+ return 0;
}
- /* If software sync is disabled or not available, try to update all. */
- if (!(VbGetSystemPropertyInt("vdat_flags") & VBSD_EC_SOFTWARE_SYNC))
- return NULL;
-
- /*
- * This is the only case that we "may" safely update EC RO without
- * sysjump, and have RW updated in next boot.
- */
- WARN("EC Software Sync detected, will only update EC RO. "
- "The contents in EC RW will be updated after reboot.\n");
- return ec_ro;
+ /* Do full update. */
+ WARN("Update EC RO+RW and may cause unexpected error later. "
+ "See http://crbug.com/782427#c4 for more information.\n");
+ return write_optional_firmware(cfg, ec_image, NULL, 1, 0);
}
const char * const updater_error_messages[] = {
@@ -1712,8 +1826,7 @@ static enum updater_error_codes update_whole_firmware(
/* FMAP may be different so we should just update all. */
if (write_firmware(cfg, image_to, NULL) ||
- write_optional_firmware(cfg, &cfg->ec_image,
- get_ec_section_to_recover(cfg), 1, 0) ||
+ update_ec_firmware(cfg) ||
write_optional_firmware(cfg, &cfg->pd_image, NULL, 1, 0))
return UPDATE_ERR_WRITE_FIRMWARE;