517 lines
13 KiB
C
517 lines
13 KiB
C
/* Copyright 2016 The Chromium OS Authors. All rights reserved.
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
#include "ccd_config.h"
|
|
#include "console.h"
|
|
#include "crc8.h"
|
|
#include "ec_commands.h"
|
|
#include "extension.h"
|
|
#include "flash_log.h"
|
|
#include "gpio.h"
|
|
#include "hooks.h"
|
|
#include "registers.h"
|
|
#include "scratch_reg1.h"
|
|
#include "system.h"
|
|
#include "system_chip.h"
|
|
#include "tpm_nvmem.h"
|
|
#include "tpm_nvmem_ops.h"
|
|
#include "tpm_registers.h"
|
|
#include "util.h"
|
|
|
|
#define CPRINTS(format, args...) cprints(CC_RBOX, format, ## args)
|
|
#define CPRINTF(format, args...) cprintf(CC_RBOX, format, ## args)
|
|
|
|
uint8_t bp_connect;
|
|
uint8_t bp_forced;
|
|
/**
|
|
* Return non-zero if battery is present
|
|
*/
|
|
int board_battery_is_present(void)
|
|
{
|
|
/* Invert because battery-present signal is active low */
|
|
return bp_forced ? bp_connect : !gpio_get_level(GPIO_BATT_PRES_L);
|
|
}
|
|
|
|
/**
|
|
* Return non-zero if the wp state is being overridden.
|
|
*/
|
|
static int board_forcing_wp(void)
|
|
{
|
|
return GREG32(PMU, LONG_LIFE_SCRATCH1) & BOARD_FORCING_WP;
|
|
}
|
|
|
|
/**
|
|
* Set the current write protect state in RBOX and long life scratch register.
|
|
*
|
|
* @param asserted: 0 to disable write protect, otherwise enable write protect.
|
|
*/
|
|
static void set_wp_state(int asserted)
|
|
{
|
|
/* Enable writing to the long life register */
|
|
GWRITE_FIELD(PMU, LONG_LIFE_SCRATCH_WR_EN, REG1, 1);
|
|
|
|
if (asserted) {
|
|
GREG32(PMU, LONG_LIFE_SCRATCH1) |= BOARD_WP_ASSERTED;
|
|
GREG32(RBOX, EC_WP_L) = 0;
|
|
} else {
|
|
GREG32(PMU, LONG_LIFE_SCRATCH1) &= ~BOARD_WP_ASSERTED;
|
|
GREG32(RBOX, EC_WP_L) = 1;
|
|
}
|
|
|
|
/* Disable writing to the long life register */
|
|
GWRITE_FIELD(PMU, LONG_LIFE_SCRATCH_WR_EN, REG1, 0);
|
|
}
|
|
|
|
/**
|
|
* Return the current WP state
|
|
*
|
|
* @return 0 if WP deasserted, 1 if WP asserted
|
|
*/
|
|
int wp_is_asserted(void)
|
|
{
|
|
/* Signal is active low, so invert */
|
|
return !GREG32(RBOX, EC_WP_L);
|
|
}
|
|
|
|
static void check_wp_battery_presence(void)
|
|
{
|
|
int bp = board_battery_is_present();
|
|
|
|
/* If we're forcing WP, ignore battery detect */
|
|
if (board_forcing_wp())
|
|
return;
|
|
|
|
/* Otherwise, mirror battery */
|
|
if (bp != wp_is_asserted()) {
|
|
CPRINTS("WP %d", bp);
|
|
set_wp_state(bp);
|
|
}
|
|
}
|
|
DECLARE_HOOK(HOOK_SECOND, check_wp_battery_presence, HOOK_PRIO_DEFAULT);
|
|
|
|
/**
|
|
* Force write protect state or follow battery presence.
|
|
*
|
|
* @param force: Force write protect to wp_en if non-zero, otherwise use battery
|
|
* presence as the source.
|
|
* @param wp_en: 0: Deassert WP. 1: Assert WP.
|
|
*/
|
|
static void force_write_protect(int force, int wp_en)
|
|
{
|
|
/* Enable writing to the long life register */
|
|
GWRITE_FIELD(PMU, LONG_LIFE_SCRATCH_WR_EN, REG1, 1);
|
|
|
|
if (force) {
|
|
/* Force WP regardless of battery presence. */
|
|
GREG32(PMU, LONG_LIFE_SCRATCH1) |= BOARD_FORCING_WP;
|
|
} else {
|
|
/* Stop forcing write protect. */
|
|
GREG32(PMU, LONG_LIFE_SCRATCH1) &= ~BOARD_FORCING_WP;
|
|
/* Use battery presence as the value for write protect. */
|
|
wp_en = board_battery_is_present();
|
|
}
|
|
|
|
/* Disable writing to the long life register */
|
|
GWRITE_FIELD(PMU, LONG_LIFE_SCRATCH_WR_EN, REG1, 0);
|
|
|
|
/* Update the WP state. */
|
|
set_wp_state(wp_en);
|
|
}
|
|
|
|
static enum vendor_cmd_rc vc_set_wp(enum vendor_cmd_cc code,
|
|
void *buf,
|
|
size_t input_size,
|
|
size_t *response_size)
|
|
{
|
|
uint8_t response = 0;
|
|
|
|
*response_size = 0;
|
|
/* There shouldn't be any args */
|
|
if (input_size)
|
|
return VENDOR_RC_BOGUS_ARGS;
|
|
|
|
/* Get current wp settings */
|
|
if (board_forcing_wp())
|
|
response |= WPV_FORCE;
|
|
if (wp_is_asserted())
|
|
response |= WPV_ENABLE;
|
|
/* Get atboot wp settings */
|
|
if (ccd_get_flag(CCD_FLAG_OVERRIDE_WP_AT_BOOT)) {
|
|
response |= WPV_ATBOOT_SET;
|
|
if (ccd_get_flag(CCD_FLAG_OVERRIDE_WP_STATE_ENABLED))
|
|
response |= WPV_ATBOOT_ENABLE;
|
|
}
|
|
((uint8_t *)buf)[0] = response;
|
|
*response_size = sizeof(response);
|
|
return VENDOR_RC_SUCCESS;
|
|
}
|
|
DECLARE_VENDOR_COMMAND(VENDOR_CC_WP, vc_set_wp);
|
|
|
|
static int command_bpforce(int argc, char **argv)
|
|
{
|
|
int val = 1;
|
|
int forced = 1;
|
|
|
|
if (argc > 1) {
|
|
/* Make sure we're allowed to override battery presence */
|
|
if (!ccd_is_cap_enabled(CCD_CAP_OVERRIDE_BATT_STATE))
|
|
return EC_ERROR_ACCESS_DENIED;
|
|
|
|
/* Update BP */
|
|
if (!strncasecmp(argv[1], "follow", 6))
|
|
forced = 0;
|
|
else if (!strncasecmp(argv[1], "dis", 3))
|
|
val = 0;
|
|
else if (strncasecmp(argv[1], "con", 3))
|
|
return EC_ERROR_PARAM2;
|
|
|
|
bp_forced = forced;
|
|
bp_connect = val;
|
|
|
|
if (argc > 2 && !strcasecmp(argv[2], "atboot")) {
|
|
/* Change override at boot to match */
|
|
ccd_set_flag(CCD_FLAG_OVERRIDE_BATT_AT_BOOT, bp_forced);
|
|
ccd_set_flag(CCD_FLAG_OVERRIDE_BATT_STATE_CONNECT,
|
|
bp_connect);
|
|
}
|
|
/* Update the WP state based on new battery presence setting */
|
|
check_wp_battery_presence();
|
|
}
|
|
|
|
ccprintf("batt pres: %s%sconnect\n", bp_forced ? "forced " : "",
|
|
board_battery_is_present() ? "" : "dis");
|
|
ccprintf(" at boot: ");
|
|
if (ccd_get_flag(CCD_FLAG_OVERRIDE_BATT_AT_BOOT))
|
|
ccprintf("forced %sconnect\n",
|
|
ccd_get_flag(CCD_FLAG_OVERRIDE_BATT_STATE_CONNECT) ? ""
|
|
: "dis");
|
|
else
|
|
ccprintf("follow_batt_pres\n");
|
|
return EC_SUCCESS;
|
|
}
|
|
DECLARE_SAFE_CONSOLE_COMMAND(bpforce, command_bpforce,
|
|
"[connect|disconnect|follow_batt_pres [atboot]]",
|
|
"Get/set BATT_PRES_L signal override");
|
|
|
|
static int command_wp(int argc, char **argv)
|
|
{
|
|
int val;
|
|
int forced;
|
|
|
|
if (argc > 1) {
|
|
/* Make sure we're allowed to override WP settings */
|
|
if (!ccd_is_cap_enabled(CCD_CAP_OVERRIDE_WP))
|
|
return EC_ERROR_ACCESS_DENIED;
|
|
|
|
/* Update WP */
|
|
if (!strncasecmp(argv[1], "follow", 6))
|
|
forced = 0;
|
|
else if (parse_bool(argv[1], &val))
|
|
forced = 1;
|
|
else
|
|
return EC_ERROR_PARAM1;
|
|
|
|
force_write_protect(forced, val);
|
|
|
|
if (argc > 2 && !strcasecmp(argv[2], "atboot")) {
|
|
/* Change override at boot to match */
|
|
ccd_set_flag(CCD_FLAG_OVERRIDE_WP_AT_BOOT, forced);
|
|
ccd_set_flag(CCD_FLAG_OVERRIDE_WP_STATE_ENABLED, val);
|
|
}
|
|
}
|
|
|
|
ccprintf("Flash WP: %s%sabled\n", board_forcing_wp() ? "forced " : "",
|
|
wp_is_asserted() ? "en" : "dis");
|
|
ccprintf(" at boot: ");
|
|
if (ccd_get_flag(CCD_FLAG_OVERRIDE_WP_AT_BOOT))
|
|
ccprintf("forced %sabled\n",
|
|
ccd_get_flag(CCD_FLAG_OVERRIDE_WP_STATE_ENABLED)
|
|
? "en" : "dis");
|
|
else
|
|
ccprintf("follow_batt_pres\n");
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
DECLARE_SAFE_CONSOLE_COMMAND(wp, command_wp,
|
|
"[<BOOLEAN>/follow_batt_pres [atboot]]",
|
|
"Get/set the flash HW write-protect signal");
|
|
|
|
void set_bp_follow_ccd_config(void)
|
|
{
|
|
if (ccd_get_flag(CCD_FLAG_OVERRIDE_BATT_AT_BOOT)) {
|
|
/* Reset to at-boot state specified by CCD */
|
|
bp_forced = 1;
|
|
bp_connect = ccd_get_flag(CCD_FLAG_OVERRIDE_BATT_STATE_CONNECT);
|
|
} else {
|
|
bp_forced = 0;
|
|
}
|
|
}
|
|
|
|
static void set_wp_follow_ccd_config(void)
|
|
{
|
|
if (ccd_get_flag(CCD_FLAG_OVERRIDE_WP_AT_BOOT)) {
|
|
/* Reset to at-boot state specified by CCD */
|
|
force_write_protect(1, ccd_get_flag
|
|
(CCD_FLAG_OVERRIDE_WP_STATE_ENABLED));
|
|
} else {
|
|
/* Reset to WP based on battery-present (val is ignored) */
|
|
force_write_protect(0, 1);
|
|
}
|
|
}
|
|
|
|
void board_wp_follow_ccd_config(void)
|
|
{
|
|
/*
|
|
* Battery presence can be overidden using CCD. Get that setting before
|
|
* configuring write protect.
|
|
*/
|
|
set_bp_follow_ccd_config();
|
|
|
|
/* Update write protect setting based on ccd config */
|
|
set_wp_follow_ccd_config();
|
|
}
|
|
|
|
void init_wp_state(void)
|
|
{
|
|
/*
|
|
* Battery presence can be overidden using CCD. Get that setting before
|
|
* configuring write protect.
|
|
*/
|
|
set_bp_follow_ccd_config();
|
|
|
|
/* Check system reset flags after CCD config is initially loaded */
|
|
if ((system_get_reset_flags() & EC_RESET_FLAG_HIBERNATE) &&
|
|
!system_rollback_detected()) {
|
|
/*
|
|
* Deep sleep resume without rollback, so reload the WP state
|
|
* that was saved to the long-life registers before the deep
|
|
* sleep instead of going back to the at-boot default.
|
|
*/
|
|
if (board_forcing_wp()) {
|
|
/* Temporarily forcing WP */
|
|
set_wp_state(GREG32(PMU, LONG_LIFE_SCRATCH1) &
|
|
BOARD_WP_ASSERTED);
|
|
} else {
|
|
/* Write protected if battery is present */
|
|
set_wp_state(board_battery_is_present());
|
|
}
|
|
} else {
|
|
set_wp_follow_ccd_config();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wipe the TPM
|
|
*
|
|
* @param reset_required: reset the system after wiping the TPM.
|
|
*
|
|
* @return EC_SUCCESS, or non-zero if error.
|
|
*/
|
|
int board_wipe_tpm(int reset_required)
|
|
{
|
|
int rc;
|
|
|
|
/* Wipe the TPM's memory and reset the TPM task. */
|
|
rc = tpm_reset_request(1, 1);
|
|
if (rc != EC_SUCCESS) {
|
|
flash_log_add_event(FE_LOG_TPM_WIPE_ERROR, 0, NULL);
|
|
/*
|
|
* If anything goes wrong (which is unlikely), we REALLY don't
|
|
* want to unlock the console. It's possible to fail without
|
|
* the TPM task ever running, so rebooting is probably our best
|
|
* bet for fixing the problem.
|
|
*/
|
|
CPRINTS("%s: Couldn't wipe nvmem! (rc %d)", __func__, rc);
|
|
cflush();
|
|
system_reset(SYSTEM_RESET_MANUALLY_TRIGGERED |
|
|
SYSTEM_RESET_HARD);
|
|
|
|
/*
|
|
* That should never return, but if it did, reset the EC and
|
|
* through the error we got.
|
|
*/
|
|
board_reboot_ec();
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* TPM was wiped out successfully, let's prevent further communications
|
|
* from the AP until next reboot. The reboot will be triggered below if
|
|
* a reset is requested. If we aren't resetting the system now, the TPM
|
|
* will stay disabled until the user resets the system.
|
|
* This should be done as soon as possible after tpm_reset_request
|
|
* completes.
|
|
*/
|
|
tpm_stop();
|
|
|
|
CPRINTS("TPM is erased");
|
|
|
|
/* Tell the TPM task to re-enable NvMem commits. */
|
|
tpm_reinstate_nvmem_commits();
|
|
|
|
/*
|
|
* Use board_reboot_ec to ensure the system resets instead of
|
|
* deassert_ec_reset. Some boards don't reset immediately when EC_RST_L
|
|
* is asserted. board_reboot_ec will ensure the system has actually
|
|
* reset before releasing it. If the system has a normal reset scheme,
|
|
* EC reset will be released immediately.
|
|
*/
|
|
if (reset_required) {
|
|
CPRINTS("%s: reset EC", __func__);
|
|
board_reboot_ec();
|
|
}
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
/****************************************************************************/
|
|
/* Verified boot TPM NVRAM space support */
|
|
|
|
/*
|
|
* These definitions and the structure layout were manually copied from
|
|
* src/platform/vboot_reference/firmware/lib/include/rollback_index.h. at
|
|
* git sha c7282f6.
|
|
*/
|
|
#define FWMP_HASH_SIZE 32
|
|
#define FWMP_DEV_DISABLE_CCD_UNLOCK BIT(6)
|
|
#define FIRMWARE_FLAG_DEV_MODE 0x02
|
|
|
|
struct RollbackSpaceFirmware {
|
|
/* Struct version, for backwards compatibility */
|
|
uint8_t struct_version;
|
|
/* Flags (see FIRMWARE_FLAG_* above) */
|
|
uint8_t flags;
|
|
/* Firmware versions */
|
|
uint32_t fw_versions;
|
|
/* Reserved for future expansion */
|
|
uint8_t reserved[3];
|
|
/* Checksum (v2 and later only) */
|
|
uint8_t crc8;
|
|
} __packed;
|
|
|
|
/* Firmware management parameters */
|
|
struct RollbackSpaceFwmp {
|
|
/* CRC-8 of fields following struct_size */
|
|
uint8_t crc;
|
|
/* Structure size in bytes */
|
|
uint8_t struct_size;
|
|
/* Structure version */
|
|
uint8_t struct_version;
|
|
/* Reserved; ignored by current reader */
|
|
uint8_t reserved0;
|
|
/* Flags; see enum fwmp_flags */
|
|
uint32_t flags;
|
|
/* Hash of developer kernel key */
|
|
uint8_t dev_key_hash[FWMP_HASH_SIZE];
|
|
} __packed;
|
|
|
|
#ifndef CR50_DEV
|
|
static int lock_enforced(const struct RollbackSpaceFwmp *fwmp)
|
|
{
|
|
uint8_t crc;
|
|
|
|
/* Let's verify that the FWMP structure makes sense. */
|
|
if (fwmp->struct_size != sizeof(*fwmp)) {
|
|
CPRINTS("%s: fwmp size mismatch (%d)", __func__,
|
|
fwmp->struct_size);
|
|
return 1;
|
|
}
|
|
|
|
crc = crc8(&fwmp->struct_version, sizeof(struct RollbackSpaceFwmp) -
|
|
offsetof(struct RollbackSpaceFwmp, struct_version));
|
|
if (fwmp->crc != crc) {
|
|
CPRINTS("%s: fwmp crc mismatch", __func__);
|
|
return 1;
|
|
}
|
|
|
|
return !!(fwmp->flags & FWMP_DEV_DISABLE_CCD_UNLOCK);
|
|
}
|
|
#endif
|
|
|
|
int board_fwmp_allows_unlock(void)
|
|
{
|
|
#ifdef CR50_DEV
|
|
return 1;
|
|
#else
|
|
/* Let's see if FWMP disables console activation. */
|
|
struct RollbackSpaceFwmp fwmp;
|
|
int allows_unlock;
|
|
|
|
switch (read_tpm_nvmem(FWMP_NV_INDEX,
|
|
sizeof(struct RollbackSpaceFwmp), &fwmp)) {
|
|
default:
|
|
/* Something is messed up, let's not allow console unlock. */
|
|
allows_unlock = 0;
|
|
break;
|
|
|
|
case tpm_read_not_found:
|
|
allows_unlock = 1;
|
|
break;
|
|
|
|
case tpm_read_success:
|
|
allows_unlock = !lock_enforced(&fwmp);
|
|
break;
|
|
}
|
|
|
|
CPRINTS("Console unlock %sallowed", allows_unlock ? "" : "not ");
|
|
|
|
return allows_unlock;
|
|
#endif
|
|
}
|
|
|
|
int board_vboot_dev_mode_enabled(void)
|
|
{
|
|
struct RollbackSpaceFirmware fw;
|
|
|
|
if (tpm_read_success ==
|
|
read_tpm_nvmem(FIRMWARE_NV_INDEX, sizeof(fw), &fw)) {
|
|
return !!(fw.flags & FIRMWARE_FLAG_DEV_MODE);
|
|
}
|
|
|
|
/* If not found or other error, assume dev mode is disabled */
|
|
return 0;
|
|
}
|
|
|
|
/****************************************************************************/
|
|
/* TPM vendor-specific commands */
|
|
|
|
static enum vendor_cmd_rc vc_lock(enum vendor_cmd_cc code,
|
|
void *buf,
|
|
size_t input_size,
|
|
size_t *response_size)
|
|
{
|
|
uint8_t *buffer = buf;
|
|
|
|
if (code == VENDOR_CC_GET_LOCK) {
|
|
/*
|
|
* Get the state of the console lock.
|
|
*
|
|
* Args: none
|
|
* Returns: one byte; true (locked) or false (unlocked)
|
|
*/
|
|
if (input_size != 0) {
|
|
*response_size = 0;
|
|
return VENDOR_RC_BOGUS_ARGS;
|
|
}
|
|
|
|
buffer[0] = console_is_restricted() ? 0x01 : 0x00;
|
|
*response_size = 1;
|
|
return VENDOR_RC_SUCCESS;
|
|
}
|
|
|
|
/* I have no idea what you're talking about */
|
|
*response_size = 0;
|
|
return VENDOR_RC_NO_SUCH_COMMAND;
|
|
}
|
|
DECLARE_VENDOR_COMMAND(VENDOR_CC_GET_LOCK, vc_lock);
|
|
|
|
/*
|
|
* TODO(rspangler): The old concept of 'lock the console' really meant
|
|
* something closer to 'reset CCD config', not the CCD V1 meaning of 'ccdlock'.
|
|
* This command is no longer supported, so will fail. It was defined this
|
|
* way:
|
|
*
|
|
* DECLARE_VENDOR_COMMAND(VENDOR_CC_SET_LOCK, vc_lock);
|
|
*/
|