/* Test SafeMemcmp */
static void SafeMemcmpTest(void) {
  /* Zero-length strings are equal */
  TEST_EQ(0, SafeMemcmp("APPLE", "TIGER", 0), "SafeMemcmp() size=0");

  /* Test equal arrays */
  TEST_EQ(0, SafeMemcmp("clonebob", "clonebob", 8), "SafeMemcmp() equal");
  /* Inequality past end of array doesn't affect result */
  TEST_EQ(0, SafeMemcmp("clonebob", "clonedan", 5), "SafeMemcmp() equal2");

  TEST_EQ(1, SafeMemcmp("APPLE", "TIGER", 5), "SafeMemcmp() unequal");
  TEST_EQ(1, SafeMemcmp("APPLE", "APPLe", 5), "SafeMemcmp() unequal 2");
}
예제 #2
0
/* Get charger power info in Watts.  Also returns type of charger */
int google_chromeec_get_usb_pd_power_info(enum usb_chg_type *type,
					  u32 *max_watts)
{
	struct ec_params_usb_pd_power_info req = {
		.port = PD_POWER_CHARGING_PORT,
	};
	struct ec_response_usb_pd_power_info rsp;
	struct chromeec_command cmd = {
		.cmd_code = EC_CMD_USB_PD_POWER_INFO,
		.cmd_version = 0,
		.cmd_data_in = &req,
		.cmd_size_in = sizeof(req),
		.cmd_data_out = &rsp,
		.cmd_size_out = sizeof(rsp),
		.cmd_dev_index = 0,
	};
	struct usb_chg_measures m;
	int rv = google_chromeec_command(&cmd);
	if (rv != 0)
		return rv;
	/* values are given in milliAmps and milliVolts */
	*type = rsp.type;
	m = rsp.meas;
	*max_watts = (m.current_max * m.voltage_max) / 1000000;

	return 0;
}

int google_chromeec_override_dedicated_charger_limit(u16 current_lim,
						     u16 voltage_lim)
{
	struct ec_params_dedicated_charger_limit p = {
		.current_lim = current_lim,
		.voltage_lim = voltage_lim,
	};
	struct chromeec_command cmd = {
		.cmd_code = EC_CMD_OVERRIDE_DEDICATED_CHARGER_LIMIT,
		.cmd_version = 0,
		.cmd_data_in = &p,
		.cmd_size_in = sizeof(p),
		.cmd_data_out = NULL,
		.cmd_size_out = 0,
		.cmd_dev_index = 0,
	};

	return google_chromeec_command(&cmd);
}

int google_chromeec_set_usb_pd_role(u8 port, enum usb_pd_control_role role)
{
	struct ec_params_usb_pd_control req = {
		.port = port,
		.role = role,
		.mux = USB_PD_CTRL_MUX_NO_CHANGE,
		.swap = USB_PD_CTRL_SWAP_NONE,
	};
	struct ec_response_usb_pd_control rsp;
	struct chromeec_command cmd = {
		.cmd_code = EC_CMD_USB_PD_CONTROL,
		.cmd_version = 0,
		.cmd_data_in = &req,
		.cmd_size_in = sizeof(req),
		.cmd_data_out = &rsp,
		.cmd_size_out = sizeof(rsp),
		.cmd_dev_index = 0,
	};

	return google_chromeec_command(&cmd);
}

static int google_chromeec_hello(void)
{
	struct chromeec_command cec_cmd;
	struct ec_params_hello cmd_hello;
	struct ec_response_hello rsp_hello;
	cmd_hello.in_data = 0x10203040;
	cec_cmd.cmd_code = EC_CMD_HELLO;
	cec_cmd.cmd_version = 0;
	cec_cmd.cmd_data_in = &cmd_hello.in_data;
	cec_cmd.cmd_data_out = &rsp_hello.out_data;
	cec_cmd.cmd_size_in = sizeof(cmd_hello.in_data);
	cec_cmd.cmd_size_out = sizeof(rsp_hello.out_data);
	cec_cmd.cmd_dev_index = 0;
	google_chromeec_command(&cec_cmd);
	printk(BIOS_DEBUG, "Google Chrome EC: Hello got back %x status (%x)\n",
	       rsp_hello.out_data, cec_cmd.cmd_code);
	return cec_cmd.cmd_code;
}

/* Cached EC image type (ro or rw). */
static int ec_image_type = EC_IMAGE_UNKNOWN;

static void google_chromeec_get_version(void)
{
	struct chromeec_command cec_cmd;
	struct ec_response_get_version cec_resp;
	cec_cmd.cmd_code = EC_CMD_GET_VERSION;
	cec_cmd.cmd_version = 0;
	cec_cmd.cmd_data_in = 0;
	cec_cmd.cmd_data_out = &cec_resp;
	cec_cmd.cmd_size_in = 0;
	cec_cmd.cmd_size_out = sizeof(cec_resp);
	cec_cmd.cmd_dev_index = 0;
	google_chromeec_command(&cec_cmd);

	if (cec_cmd.cmd_code) {
		printk(BIOS_DEBUG,
		       "Google Chrome EC: version command failed!\n");
	} else {
		printk(BIOS_DEBUG, "Google Chrome EC: version:\n");
		printk(BIOS_DEBUG, "    ro: %s\n", cec_resp.version_string_ro);
		printk(BIOS_DEBUG, "    rw: %s\n", cec_resp.version_string_rw);
		printk(BIOS_DEBUG, "  running image: %d\n",
		       cec_resp.current_image);
		ec_image_type = cec_resp.current_image;
	}
}

void google_chromeec_init(void)
{
	printk(BIOS_DEBUG, "Google Chrome EC: Initializing\n");

	google_chromeec_hello();

	/* Get version to check which EC image is active */
	google_chromeec_get_version();

	/* Check/update EC RW image if needed */
	if (google_chromeec_swsync()) {
		printk(BIOS_ERR, "ChromeEC: EC SW SYNC FAILED\n");
	} else if (ec_image_type != EC_IMAGE_RW) {
		/* EC RW image is up to date, switch to it if not already*/
		google_chromeec_reboot(0, EC_REBOOT_JUMP_RW, 0);
		mdelay(100);
		/* Use Hello cmd to "reset" EC now in RW mode */
		google_chromeec_hello();
		/* re-run version command & print */
		google_chromeec_get_version();
	}
}

int google_ec_running_ro(void)
{
	return (ec_image_type == EC_IMAGE_RO);
}

void google_chromeec_reboot_ro(void)
{
	/* Reboot the EC and make it come back in RO mode */
	printk(BIOS_DEBUG, "Rebooting with EC in RO mode:\n");
	post_code(0); /* clear current post code */
	google_chromeec_reboot(0, EC_REBOOT_COLD, 0);
	udelay(1000);
	board_reset();
	halt();
}

/* Timeout waiting for EC hash calculation completion */
static const int CROS_EC_HASH_TIMEOUT_MS = 2000;

/* Time to delay between polling status of EC hash calculation */
static const int CROS_EC_HASH_CHECK_DELAY_MS = 10;

int google_chromeec_swsync(void)
{
	static struct ec_response_vboot_hash resp;
	uint8_t *ec_hash;
	int ec_hash_size;
	uint8_t *ecrw_hash, *ecrw;
	int need_update = 0, i;
	size_t ecrw_size;

	/* skip if on S3 resume path */
	if (acpi_is_wakeup_s3())
		return 0;

	/* Get EC_RW hash from CBFS */
	ecrw_hash = cbfs_boot_map_with_leak("ecrw.hash", CBFS_TYPE_RAW, NULL);

	if (!ecrw_hash) {
		/* Assume no EC update file for this board */
		printk(BIOS_DEBUG, "ChromeEC SW Sync: no EC_RW update available\n");
		return 0;
	}

	/* Got an expected hash */
	printk(BIOS_DEBUG, "ChromeEC SW Sync: Expected hash: ");
	for (i = 0; i < SHA256_DIGEST_SIZE; i++)
		printk(BIOS_DEBUG, "%02x", ecrw_hash[i]);
	printk(BIOS_DEBUG, "\n");

	/* Get hash of current EC-RW */
	if (google_chromeec_read_hash(&resp)) {
		printk(BIOS_ERR, "Failed to read current EC_RW hash.\n");
		return -1;
	}
	ec_hash = resp.hash_digest;
	ec_hash_size = resp.digest_size;
	/* Check hash size */
	if (ec_hash_size != SHA256_DIGEST_SIZE) {
		printk(BIOS_ERR, "ChromeEC SW Sync: - "
			 "read_hash says size %d, not %d\n",
			 ec_hash_size, SHA256_DIGEST_SIZE);
		return -1;
	}

	/* We got a proper hash */
	printk(BIOS_DEBUG, "ChromeEC SW Sync: current EC_RW hash: ");
	for (i = 0; i < SHA256_DIGEST_SIZE; i++)
		printk(BIOS_DEBUG, "%02x", ec_hash[i]);
	printk(BIOS_DEBUG, "\n");

	/* compare hashes */
	need_update = SafeMemcmp(ec_hash, ecrw_hash, SHA256_DIGEST_SIZE);

	/* If in RW and need to update, return/reboot to RO */
	if (need_update && ec_image_type == EC_IMAGE_RW) {
		printk(BIOS_DEBUG, "ChromeEC SW Sync: EC_RW needs update but in RW; rebooting to RO\n");
		google_chromeec_reboot_ro();
		return -1;
	}

	/* Update EC if necessary */
	if (need_update) {
		printk(BIOS_DEBUG, "ChromeEC SW Sync: updating EC_RW...\n");

		/* Get ecrw image from CBFS */
		ecrw = cbfs_boot_map_with_leak("ecrw", CBFS_TYPE_RAW, &ecrw_size);
		if (!ecrw) {
			printk(BIOS_ERR, "ChromeEC SW Sync: no ecrw image found in CBFS; cannot update\n");
			return -1;
		}

		if (google_chromeec_flash_update_rw(ecrw, ecrw_size)) {
			printk(BIOS_ERR, "ChromeEC SW Sync: Failed to update EC_RW.\n");
			return -1;
		}

		/* Have EC recompute hash for new EC_RW block */
		if (google_chromeec_read_hash(&resp) ) {
			printk(BIOS_ERR, "ChromeEC SW Sync: Failed to read new EC_RW hash.\n");
			return -1;
		}

		/* Compare new EC_RW hash to value from CBFS */
		ec_hash = resp.hash_digest;
		if(SafeMemcmp(ec_hash, ecrw_hash, SHA256_DIGEST_SIZE)) {
			/* hash mismatch! */
			printk(BIOS_DEBUG, "ChromeEC SW Sync: Expected hash: ");
			for (i = 0; i < SHA256_DIGEST_SIZE; i++)
				printk(BIOS_DEBUG, "%02x", ecrw_hash[i]);
			printk(BIOS_DEBUG, "\n");
			printk(BIOS_DEBUG, "ChromeEC SW Sync: EC hash: ");
			for (i = 0; i < SHA256_DIGEST_SIZE; i++)
				printk(BIOS_DEBUG, "%02x", ec_hash[i]);
			printk(BIOS_DEBUG, "\n");
			return -1;
		}
		printk(BIOS_DEBUG, "ChromeEC SW Sync: EC_RW hashes match\n");
		printk(BIOS_DEBUG, "ChromeEC SW Sync: done\n");
	} else {
		printk(BIOS_DEBUG, "ChromeEC SW Sync: EC_RW is up to date\n");
	}

	return 0;
}

int google_chromeec_read_hash(struct ec_response_vboot_hash *hash)
{
	struct chromeec_command cec_cmd;
	struct ec_params_vboot_hash p;
	int recalc_requested = 0;
	uint64_t start = timer_us(0);

	do {
		/* Get hash if available. */
		p.cmd = EC_VBOOT_HASH_GET;
		cec_cmd.cmd_code = EC_CMD_VBOOT_HASH;
		cec_cmd.cmd_version = 0;
		cec_cmd.cmd_data_in = &p;
		cec_cmd.cmd_data_out = hash;
		cec_cmd.cmd_size_in = sizeof(p);
		cec_cmd.cmd_size_out = sizeof(*hash);
		cec_cmd.cmd_dev_index = 0;
		printk(BIOS_DEBUG, "ChromeEC: Getting hash:\n");
		if (google_chromeec_command(&cec_cmd))
			return -1;

		switch (hash->status) {
		case EC_VBOOT_HASH_STATUS_NONE:
			/* We have no valid hash - let's request a recalc
			 * if we haven't done so yet. */
			if (recalc_requested != 0) {
				mdelay(CROS_EC_HASH_CHECK_DELAY_MS);
				break;
			}
			printk(BIOS_DEBUG, "ChromeEC: No valid hash (status=%d size=%d). "
			      "Compute one...\n", hash->status, hash->size);
			p.cmd = EC_VBOOT_HASH_RECALC;
			p.hash_type = EC_VBOOT_HASH_TYPE_SHA256;
			p.nonce_size = 0;
			p.offset = EC_VBOOT_HASH_OFFSET_RW;
			p.size = 0;
			cec_cmd.cmd_code = EC_CMD_VBOOT_HASH;
			cec_cmd.cmd_version = 0;
			cec_cmd.cmd_data_in = &p;
			cec_cmd.cmd_data_out = hash;
			cec_cmd.cmd_size_in = sizeof(p);
			cec_cmd.cmd_size_out = sizeof(*hash);
			cec_cmd.cmd_dev_index = 0;
			printk(BIOS_DEBUG, "ChromeEC: Starting EC hash:\n");
			if (google_chromeec_command(&cec_cmd))
				return -1;
			recalc_requested = 1;
			/* Command will wait to return until hash is done/ready */
			break;
		case EC_VBOOT_HASH_STATUS_BUSY:
			/* Hash is still calculating. */
			mdelay(CROS_EC_HASH_CHECK_DELAY_MS);
			break;
		case EC_VBOOT_HASH_STATUS_DONE:
		default:
			/* We have a valid hash. */
			break;
		}
	} while (hash->status != EC_VBOOT_HASH_STATUS_DONE &&
		 timer_us(start) < CROS_EC_HASH_TIMEOUT_MS * 1000);
	if (hash->status != EC_VBOOT_HASH_STATUS_DONE) {
		printk(BIOS_DEBUG, "ChromeEC: Hash status not done: %d\n", hash->status);
		return -1;
	}
	return 0;
}

int google_chromeec_flash_update_rw(const uint8_t *image, int image_size)
{
	uint32_t rw_offset, rw_size;
	int ret;

	/* get max size that can be written, offset to write */
	if (google_chromeec_flash_offset(EC_FLASH_REGION_RW, &rw_offset, &rw_size))
		return -1;
	if (image_size > rw_size)
		return -1;
	/*
	 * Erase the entire RW section, so that the EC doesn't see any garbage
	 * past the new image if it's smaller than the current image.
	 *
	 */
	ret = google_chromeec_flash_erase(rw_offset, rw_size);
	if (ret)
		return ret;
	/* Write the image */
	return(google_chromeec_flash_write(image, rw_offset, image_size));
}
예제 #3
0
int KeyBlockVerify(const VbKeyBlockHeader *block, uint64_t size,
                   const VbPublicKey *key, int hash_only)
{
	const VbSignature *sig;

	/* Sanity checks before attempting signature of data */
	if(size < sizeof(VbKeyBlockHeader)) {
		VBDEBUG(("Not enough space for key block header.\n"));
		return VBOOT_KEY_BLOCK_INVALID;
	}
	if (SafeMemcmp(block->magic, KEY_BLOCK_MAGIC, KEY_BLOCK_MAGIC_SIZE)) {
		VBDEBUG(("Not a valid verified boot key block.\n"));
		return VBOOT_KEY_BLOCK_INVALID;
	}
	if (block->header_version_major != KEY_BLOCK_HEADER_VERSION_MAJOR) {
		VBDEBUG(("Incompatible key block header version.\n"));
		return VBOOT_KEY_BLOCK_INVALID;
	}
	if (size < block->key_block_size) {
		VBDEBUG(("Not enough data for key block.\n"));
		return VBOOT_KEY_BLOCK_INVALID;
	}
	if (!hash_only && !key) {
		VBDEBUG(("Missing required public key.\n"));
		return VBOOT_PUBLIC_KEY_INVALID;
	}

	/*
	 * Check signature or hash, depending on the hash_only parameter. Note
	 * that we don't require a key even if the keyblock has a signature,
	 * because the caller may not care if the keyblock itself is signed
	 * (for example, booting a Google-signed kernel in developer mode).
	 */
	if (hash_only) {
		/* Check hash */
		uint8_t *header_checksum = NULL;
		int rv;

		sig = &block->key_block_checksum;

		if (VerifySignatureInside(block, block->key_block_size, sig)) {
			VBDEBUG(("Key block hash off end of block\n"));
			return VBOOT_KEY_BLOCK_INVALID;
		}
		if (sig->sig_size != SHA512_DIGEST_SIZE) {
			VBDEBUG(("Wrong hash size for key block.\n"));
			return VBOOT_KEY_BLOCK_INVALID;
		}

		/* Make sure advertised signature data sizes are sane. */
		if (block->key_block_size < sig->data_size) {
			VBDEBUG(("Signature calculated past end of block\n"));
			return VBOOT_KEY_BLOCK_INVALID;
		}

		VBDEBUG(("Checking key block hash only...\n"));
		header_checksum = DigestBuf((const uint8_t *)block,
					    sig->data_size,
					    SHA512_DIGEST_ALGORITHM);
		rv = SafeMemcmp(header_checksum, GetSignatureDataC(sig),
				SHA512_DIGEST_SIZE);
		VbExFree(header_checksum);
		if (rv) {
			VBDEBUG(("Invalid key block hash.\n"));
			return VBOOT_KEY_BLOCK_HASH;
		}
	} else {
		/* Check signature */
		RSAPublicKey *rsa;
		int rv;

		sig = &block->key_block_signature;

		if (VerifySignatureInside(block, block->key_block_size, sig)) {
			VBDEBUG(("Key block signature off end of block\n"));
			return VBOOT_KEY_BLOCK_INVALID;
		}

		rsa = PublicKeyToRSA(key);
		if (!rsa) {
			VBDEBUG(("Invalid public key\n"));
			return VBOOT_PUBLIC_KEY_INVALID;
		}

		/* Make sure advertised signature data sizes are sane. */
		if (block->key_block_size < sig->data_size) {
			VBDEBUG(("Signature calculated past end of block\n"));
			RSAPublicKeyFree(rsa);
			return VBOOT_KEY_BLOCK_INVALID;
		}

		VBDEBUG(("Checking key block signature...\n"));
		rv = VerifyData((const uint8_t *)block, size, sig, rsa);
		RSAPublicKeyFree(rsa);
		if (rv) {
			VBDEBUG(("Invalid key block signature.\n"));
			return VBOOT_KEY_BLOCK_SIGNATURE;
		}
	}

	/* Verify we signed enough data */
	if (sig->data_size < sizeof(VbKeyBlockHeader)) {
		VBDEBUG(("Didn't sign enough data\n"));
		return VBOOT_KEY_BLOCK_INVALID;
	}

	/* Verify data key is inside the block and inside signed data */
	if (VerifyPublicKeyInside(block, block->key_block_size,
				  &block->data_key)) {
		VBDEBUG(("Data key off end of key block\n"));
		return VBOOT_KEY_BLOCK_INVALID;
	}
	if (VerifyPublicKeyInside(block, sig->data_size, &block->data_key)) {
		VBDEBUG(("Data key off end of signed data\n"));
		return VBOOT_KEY_BLOCK_INVALID;
	}

	/* Success */
	return VBOOT_SUCCESS;
}
예제 #4
0
VbError_t VbEcSoftwareSync(int devidx, VbCommonParams *cparams)
{
    VbSharedDataHeader *shared =
        (VbSharedDataHeader *)cparams->shared_data_blob;
    int in_rw = 0;
    int rv;
    const uint8_t *ec_hash = NULL;
    int ec_hash_size;
    const uint8_t *rw_hash = NULL;
    int rw_hash_size;
    const uint8_t *expected = NULL;
    int expected_size;
    uint8_t expected_hash[SHA256_DIGEST_SIZE];
    int need_update = 0;
    int i;

    VBDEBUG(("VbEcSoftwareSync(devidx=%d)\n", devidx));

    /* Determine whether the EC is in RO or RW */
    rv = VbExEcRunningRW(devidx, &in_rw);

    if (shared->recovery_reason) {
        /* Recovery mode; just verify the EC is in RO code */
        if (rv == VBERROR_SUCCESS && in_rw == 1) {
            /*
             * EC is definitely in RW firmware.  We want it in
             * read-only code, so preserve the current recovery
             * reason and reboot.
             *
             * We don't reboot on error or unknown EC code, because
             * we could end up in an endless reboot loop.  If we
             * had some way to track that we'd already rebooted for
             * this reason, we could retry only once.
             */
            VBDEBUG(("VbEcSoftwareSync() - "
                     "want recovery but got EC-RW\n"));
            VbSetRecoveryRequest(shared->recovery_reason);
            return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
        }

        VBDEBUG(("VbEcSoftwareSync() in recovery; EC-RO\n"));
        return VBERROR_SUCCESS;
    }

    /*
     * Not in recovery.  If we couldn't determine where the EC was,
     * reboot to recovery.
     */
    if (rv != VBERROR_SUCCESS) {
        VBDEBUG(("VbEcSoftwareSync() - "
                 "VbExEcRunningRW() returned %d\n", rv));
        VbSetRecoveryRequest(VBNV_RECOVERY_EC_UNKNOWN_IMAGE);
        return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
    }

    /* If AP is read-only normal, EC should be in its RO code also. */
    if (shared->flags & VBSD_LF_USE_RO_NORMAL) {
        /* If EC is in RW code, request reboot back to RO */
        if (in_rw == 1) {
            VBDEBUG(("VbEcSoftwareSync() - "
                     "want RO-normal but got EC-RW\n"));
            return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
        }

        /* Protect the RW flash and stay in EC-RO */
        rv = EcProtectRW(devidx);
        if (rv != VBERROR_SUCCESS)
            return rv;

        rv = VbExEcDisableJump(devidx);
        if (rv != VBERROR_SUCCESS) {
            VBDEBUG(("VbEcSoftwareSync() - "
                     "VbExEcDisableJump() returned %d\n", rv));
            VbSetRecoveryRequest(VBNV_RECOVERY_EC_SOFTWARE_SYNC);
            return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
        }

        VBDEBUG(("VbEcSoftwareSync() in RO-Normal; EC-RO\n"));
        return VBERROR_SUCCESS;
    }

    /* Get hash of EC-RW */
    rv = VbExEcHashRW(devidx, &ec_hash, &ec_hash_size);
    if (rv) {
        VBDEBUG(("VbEcSoftwareSync() - "
                 "VbExEcHashRW() returned %d\n", rv));
        VbSetRecoveryRequest(VBNV_RECOVERY_EC_HASH_FAILED);
        return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
    }
    if (ec_hash_size != SHA256_DIGEST_SIZE) {
        VBDEBUG(("VbEcSoftwareSync() - "
                 "VbExEcHashRW() says size %d, not %d\n",
                 ec_hash_size, SHA256_DIGEST_SIZE));
        VbSetRecoveryRequest(VBNV_RECOVERY_EC_HASH_SIZE);
        return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
    }

    VBDEBUG(("EC hash:"));
    for (i = 0; i < SHA256_DIGEST_SIZE; i++)
        VBDEBUG(("%02x", ec_hash[i]));
    VBDEBUG(("\n"));

    /*
     * Get expected EC-RW hash. Note that we've already checked for
     * RO_NORMAL, so we know that the BIOS must be RW-A or RW-B, and
     * therefore the EC must match.
     */
    rv = VbExEcGetExpectedRWHash(devidx, shared->firmware_index ?
                                 VB_SELECT_FIRMWARE_B : VB_SELECT_FIRMWARE_A,
                                 &rw_hash, &rw_hash_size);

    if (rv == VBERROR_EC_GET_EXPECTED_HASH_FROM_IMAGE) {
        /*
         * BIOS has verified EC image but doesn't have a precomputed
         * hash for it, so we must compute the hash ourselves.
         */
        rw_hash = NULL;
    } else if (rv) {
        VBDEBUG(("VbEcSoftwareSync() - "
                 "VbExEcGetExpectedRWHash() returned %d\n", rv));
        VbSetRecoveryRequest(VBNV_RECOVERY_EC_EXPECTED_HASH);
        return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
    } else if (rw_hash_size != SHA256_DIGEST_SIZE) {
        VBDEBUG(("VbEcSoftwareSync() - "
                 "VbExEcGetExpectedRWHash() says size %d, not %d\n",
                 rw_hash_size, SHA256_DIGEST_SIZE));
        VbSetRecoveryRequest(VBNV_RECOVERY_EC_EXPECTED_HASH);
        return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
    } else {
        VBDEBUG(("Expected hash:"));
        for (i = 0; i < SHA256_DIGEST_SIZE; i++)
            VBDEBUG(("%02x", rw_hash[i]));
        VBDEBUG(("\n"));

        need_update = SafeMemcmp(ec_hash, rw_hash, SHA256_DIGEST_SIZE);
    }

    /*
     * Get expected EC-RW image if we're sure we need to update (because the
     * expected hash didn't match the EC) or we still don't know (because
     * there was no expected hash and we need the image to compute one
     * ourselves).
     */
    if (need_update || !rw_hash) {
        /* Get expected EC-RW image */
        rv = VbExEcGetExpectedRW(devidx, shared->firmware_index ?
                                 VB_SELECT_FIRMWARE_B :
                                 VB_SELECT_FIRMWARE_A,
                                 &expected, &expected_size);
        if (rv) {
            VBDEBUG(("VbEcSoftwareSync() - "
                     "VbExEcGetExpectedRW() returned %d\n", rv));
            VbSetRecoveryRequest(VBNV_RECOVERY_EC_EXPECTED_IMAGE);
            return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
        }
        VBDEBUG(("VbEcSoftwareSync() - expected len = %d\n",
                 expected_size));

        /* Hash expected image */
        internal_SHA256(expected, expected_size, expected_hash);
        VBDEBUG(("Computed hash of expected image:"));
        for (i = 0; i < SHA256_DIGEST_SIZE; i++)
            VBDEBUG(("%02x", expected_hash[i]));
        VBDEBUG(("\n"));
    }

    if (!rw_hash) {
        /*
         * BIOS didn't have expected EC hash, so check if we need
         * update by comparing EC hash to the one we just computed.
         */
        need_update = SafeMemcmp(ec_hash, expected_hash,
                                 SHA256_DIGEST_SIZE);
    } else if (need_update &&
               SafeMemcmp(rw_hash, expected_hash, SHA256_DIGEST_SIZE)) {
        /*
         * We need to update, but the expected EC image doesn't match
         * the expected EC hash we were given.
         */
        VBDEBUG(("VbEcSoftwareSync() - "
                 "VbExEcGetExpectedRW() returned %d\n", rv));
        VbSetRecoveryRequest(VBNV_RECOVERY_EC_HASH_MISMATCH);
        return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
    }

    /*
     * TODO: GBB flag to override whether we need update; needed for EC
     * development.
     */

    if (in_rw) {
        if (need_update) {
            /*
             * Check if BIOS should also load VGA Option ROM when
             * rebooting to save another reboot if possible.
             */
            if ((shared->flags & VBSD_EC_SLOW_UPDATE) &&
                    (shared->flags & VBSD_OPROM_MATTERS) &&
                    !(shared->flags & VBSD_OPROM_LOADED)) {
                VBDEBUG(("VbEcSoftwareSync() - Reboot to "
                         "load VGA Option ROM\n"));
                VbNvSet(&vnc, VBNV_OPROM_NEEDED, 1);
            }

            /*
             * EC is running the wrong RW image.  Reboot the EC to
             * RO so we can update it on the next boot.
             */
            VBDEBUG(("VbEcSoftwareSync() - "
                     "in RW, need to update RW, so reboot\n"));
            return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
        }

        VBDEBUG(("VbEcSoftwareSync() in EC-RW and it matches\n"));
        return VBERROR_SUCCESS;
    }

    /* Update EC if necessary */
    if (need_update) {
        VBDEBUG(("VbEcSoftwareSync() updating EC-RW...\n"));

        if (shared->flags & VBSD_EC_SLOW_UPDATE) {
            VBDEBUG(("VbEcSoftwareSync() - "
                     "EC is slow. Show WAIT screen.\n"));

            /* Ensure the VGA Option ROM is loaded */
            if ((shared->flags & VBSD_OPROM_MATTERS) &&
                    !(shared->flags & VBSD_OPROM_LOADED)) {
                VBDEBUG(("VbEcSoftwareSync() - Reboot to "
                         "load VGA Option ROM\n"));
                VbNvSet(&vnc, VBNV_OPROM_NEEDED, 1);
                return VBERROR_VGA_OPROM_MISMATCH;
            }

            VbDisplayScreen(cparams, VB_SCREEN_WAIT, 0, &vnc);
        }

        rv = VbExEcUpdateRW(devidx, expected, expected_size);

        if (rv != VBERROR_SUCCESS) {
            VBDEBUG(("VbEcSoftwareSync() - "
                     "VbExEcUpdateRW() returned %d\n", rv));

            /*
             * The EC may know it needs a reboot.  It may need to
             * unprotect RW before updating, or may need to reboot
             * after RW updated.  Either way, it's not an error
             * requiring recovery mode.
             *
             * If we fail for any other reason, trigger recovery
             * mode.
             */
            if (rv != VBERROR_EC_REBOOT_TO_RO_REQUIRED)
                VbSetRecoveryRequest(VBNV_RECOVERY_EC_UPDATE);

            return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
        }

        /*
         * TODO: should ask EC to recompute its hash to verify it's
         * correct before continuing?
         */
    }

    /* Protect EC-RW flash */
    rv = EcProtectRW(devidx);
    if (rv != VBERROR_SUCCESS)
        return rv;

    /* Tell EC to jump to its RW image */
    VBDEBUG(("VbEcSoftwareSync() jumping to EC-RW\n"));
    rv = VbExEcJumpToRW(devidx);
    if (rv != VBERROR_SUCCESS) {
        VBDEBUG(("VbEcSoftwareSync() - "
                 "VbExEcJumpToRW() returned %d\n", rv));

        /*
         * If the EC booted RO-normal and a previous AP boot has called
         * VbExEcStayInRO(), we need to reboot the EC to unlock the
         * ability to jump to the RW firmware.
         *
         * All other errors trigger recovery mode.
         */
        if (rv != VBERROR_EC_REBOOT_TO_RO_REQUIRED)
            VbSetRecoveryRequest(VBNV_RECOVERY_EC_JUMP_RW);

        return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
    }

    VBDEBUG(("VbEcSoftwareSync() jumped to EC-RW\n"));

    rv = VbExEcDisableJump(devidx);
    if (rv != VBERROR_SUCCESS) {
        VBDEBUG(("VbEcSoftwareSync() - "
                 "VbExEcDisableJump() returned %d\n", rv));
        VbSetRecoveryRequest(VBNV_RECOVERY_EC_SOFTWARE_SYNC);
        return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
    }

    /*
     * Reboot to unload VGA Option ROM if:
     * - RW update was done
     * - the system is NOT in developer mode
     * - the system has slow EC update flag set
     * - the VGA Option ROM was needed and loaded
     */
    if (need_update &&
            !(shared->flags & VBSD_BOOT_DEV_SWITCH_ON) &&
            (shared->flags & VBSD_EC_SLOW_UPDATE) &&
            (shared->flags & VBSD_OPROM_MATTERS) &&
            (shared->flags & VBSD_OPROM_LOADED)) {
        VBDEBUG(("VbEcSoftwareSync() - Reboot to "
                 "unload VGA Option ROM\n"));
        return VBERROR_VGA_OPROM_MISMATCH;
    }

    VBDEBUG(("VbEcSoftwareSync() in RW; done\n"));
    return VBERROR_SUCCESS;
}