/* Fill in active EC firmware information. */
static int set_active_ec_firmware(crossystem_data_t* cdata)
{
	int in_rw = 0;
	int rv;

	/* If software sync is disabled, just leave this as original value. */
	if (!cros_fdtdec_config_has_prop(gd->fdt_blob, "ec-software-sync")) {
		cdata->active_ec_firmware = ACTIVE_EC_FIRMWARE_UNCHANGE;
		return 0;
	}

	rv = VbExEcRunningRW(&in_rw);
	if (rv != VBERROR_SUCCESS)
		return rv;
	cdata->active_ec_firmware = (in_rw ? ACTIVE_EC_FIRMWARE_RW :
					     ACTIVE_EC_FIRMWARE_RO);
	return 0;
}
Example #2
0
int crossystem_setup(void)
{
	chromeos_acpi_t *acpi_table = (chromeos_acpi_t *)lib_sysinfo.vdat_addr;
	VbSharedDataHeader *vboot_handoff_shared_data;
	VbSharedDataHeader *vdat = (VbSharedDataHeader *)&acpi_table->vdat;
	int size;

	if (vdat->magic != VB_SHARED_DATA_MAGIC) {
		printf("Bad magic value in vboot shared data header.\n");
		return 1;
	}

	acpi_table->boot_reason = BOOT_REASON_OTHER;

	int main_fw;
	const char *fwid;
	int fwid_size;
	int fw_index = vdat->firmware_index;

	fwid = get_fw_id(fw_index);

	if (fwid == NULL) {
		printf("Unrecognized firmware index %d.\n", fw_index);
		return 1;
	}

	fwid_size = get_fw_size(fw_index);

	const struct {
		int vdat_fw_index;
		int main_fw_index;
	} main_fw_arr[] = {
		{ VDAT_RW_A, BINF_RW_A },
		{ VDAT_RW_B, BINF_RW_B },
		{ VDAT_RECOVERY, BINF_RECOVERY },
	};

	int i;
	for (i = 0; i < ARRAY_SIZE(main_fw_arr); i++) {
		if (fw_index == main_fw_arr[i].vdat_fw_index) {
			main_fw = main_fw_arr[i].main_fw_index;
			break;
		}
	}
	assert(i < ARRAY_SIZE(main_fw_arr));

	acpi_table->main_fw = main_fw;

	// Use the value set by coreboot if we don't want to change it.
	if (CONFIG_EC_SOFTWARE_SYNC) {
		int in_rw = 0;

		if (VbExEcRunningRW(0, &in_rw)) {
			printf("Couldn't tell if the EC firmware is RW.\n");
			return 1;
		}
		acpi_table->ec_fw = in_rw ? ACTIVE_ECFW_RW : ACTIVE_ECFW_RO;
	}

	uint16_t chsw = 0;
	if (flag_fetch(FLAG_WPSW))
		chsw |= CHSW_FIRMWARE_WP_DIS;
	if (flag_fetch(FLAG_RECSW))
		chsw |= CHSW_RECOVERY_X86;
	if (flag_fetch(FLAG_DEVSW))
		chsw |= CHSW_DEVELOPER_SWITCH;
	acpi_table->chsw = chsw;

	GoogleBinaryBlockHeader *gbb = cparams.gbb_data;
	if (memcmp(gbb->signature, GBB_SIGNATURE, GBB_SIGNATURE_SIZE)) {
		printf("Bad signature on GBB.\n");
		return 1;
	}
	char *hwid = (char *)((uintptr_t)cparams.gbb_data + gbb->hwid_offset);
	size = MIN(gbb->hwid_size, sizeof(acpi_table->hwid));
	memcpy(acpi_table->hwid, hwid, size);

	size = MIN(fwid_size, sizeof(acpi_table->fwid));
	memcpy(acpi_table->fwid, fwid, size);

	size = get_ro_fw_size();

	if (size) {
		size = MIN(size, sizeof(acpi_table->frid));
		memcpy(acpi_table->frid, get_ro_fw_id(), size);
	}

	if (main_fw == BINF_RECOVERY)
		acpi_table->main_fw_type = FIRMWARE_TYPE_RECOVERY;
	else if (vdat->flags & VBSD_BOOT_DEV_SWITCH_ON)
		acpi_table->main_fw_type = FIRMWARE_TYPE_DEVELOPER;
	else
		acpi_table->main_fw_type = FIRMWARE_TYPE_NORMAL;

	acpi_table->recovery_reason = vdat->recovery_reason;

	acpi_table->fmap_base = (uintptr_t)fmap_base();

	size = MIN(fwid_size, strnlen(fwid, ACPI_FWID_SIZE));
	uint8_t *dest = (uint8_t *)(uintptr_t)acpi_table->fwid_ptr;
	memcpy(dest, fwid, size);
	dest[size] = 0;

	// Synchronize the value in vboot_handoff back to acpi vdat.
	if (find_common_params((void**)(&vboot_handoff_shared_data), &size) == 0)
		memcpy(vdat, vboot_handoff_shared_data, size);
	else {
		printf("Can't find common params.\n");
		return 1;
	}

	return 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;
}