blob: 0db21f320590c69aa1d78d77ccff62e5f74d7233 [file] [log] [blame]
/*
* Copyright (c) 2013 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.
*
* Alternatively, this software may be distributed under the terms of the
* GNU General Public License ("GPL") version 2 as published by the Free
* Software Foundation.
*/
#include <common.h>
#include <asm/global_data.h>
#include <cros/common.h>
#include <cros/firmware_storage.h>
#include <cros/vboot_flag.h>
#include <malloc.h>
#include <cros_ec.h>
#include <vboot_api.h>
DECLARE_GLOBAL_DATA_PTR;
/* We support corrupting a byte of the EC image */
static int corrupt_offset = -1; /* offset into image to corrupt */
static uint8_t corrupt_byte; /* new byte value at that offset */
void cros_ec_set_corrupt_image(int offset, int byte)
{
corrupt_offset = offset;
corrupt_byte = byte;
}
int VbExTrustEC(int devidx)
{
struct vboot_flag_details gpio_ec_in_rw;
int okay;
if (devidx != 0)
return 0;
/* If we don't have a valid GPIO to read, we can't trust it. */
if (vboot_flag_fetch(VBOOT_FLAG_EC_IN_RW, &gpio_ec_in_rw)) {
VBDEBUG("can't find GPIO to read, returning 0\n");
return 0;
}
/* We only trust it if it's NOT in its RW firmware. */
okay = !gpio_ec_in_rw.value;
VBDEBUG("port=%d value=%d, returning %d\n",
gpio_ec_in_rw.port, gpio_ec_in_rw.value, okay);
return okay;
}
VbError_t VbExEcRunningRW(int devidx, int *in_rw)
{
struct cros_ec_dev *mdev = board_get_cros_ec_dev();
enum ec_current_image image;
if (devidx != 0)
return VBERROR_UNKNOWN;
if (!mdev) {
VBDEBUG("%s: no cros_ec device\n", __func__);
return VBERROR_UNKNOWN;
}
if (cros_ec_read_current_image(mdev, &image) < 0)
return VBERROR_UNKNOWN;
switch (image) {
case EC_IMAGE_RO:
*in_rw = 0;
break;
case EC_IMAGE_RW:
*in_rw = 1;
break;
default:
return VBERROR_UNKNOWN;
}
return VBERROR_SUCCESS;
}
VbError_t VbExEcJumpToRW(int devidx)
{
struct cros_ec_dev *mdev = board_get_cros_ec_dev();
if (devidx != 0)
return VBERROR_UNKNOWN;
if (!mdev) {
VBDEBUG("%s: no cros_ec device\n", __func__);
return VBERROR_UNKNOWN;
}
if (cros_ec_reboot(mdev, EC_REBOOT_JUMP_RW, 0) < 0)
return VBERROR_UNKNOWN;
return VBERROR_SUCCESS;
}
VbError_t VbExEcDisableJump(int devidx)
{
struct cros_ec_dev *mdev = board_get_cros_ec_dev();
if (devidx != 0)
return VBERROR_UNKNOWN;
if (!mdev) {
VBDEBUG("%s: no cros_ec device\n", __func__);
return VBERROR_UNKNOWN;
}
if (cros_ec_reboot(mdev, EC_REBOOT_DISABLE_JUMP, 0) < 0)
return VBERROR_UNKNOWN;
return VBERROR_SUCCESS;
}
VbError_t VbExEcHashImage(int devidx, enum VbSelectFirmware_t select,
const uint8_t **hash, int *hash_size)
{
struct cros_ec_dev *mdev = board_get_cros_ec_dev();
static struct ec_response_vboot_hash resp __aligned(8);
if (devidx != 0 || select == VB_SELECT_FIRMWARE_READONLY)
return VBERROR_UNKNOWN;
if (!mdev) {
VBDEBUG("%s: no cros_ec device\n", __func__);
return VBERROR_UNKNOWN;
}
if (cros_ec_read_hash(mdev, &resp) < 0)
return VBERROR_UNKNOWN;
if (resp.status != EC_VBOOT_HASH_STATUS_DONE)
return VBERROR_UNKNOWN;
if (resp.hash_type != EC_VBOOT_HASH_TYPE_SHA256)
return VBERROR_UNKNOWN;
*hash = resp.hash_digest;
*hash_size = resp.digest_size;
return VBERROR_SUCCESS;
}
static VbError_t ec_protect_rw(int protect)
{
struct cros_ec_dev *mdev = board_get_cros_ec_dev();
struct ec_response_flash_protect resp;
uint32_t mask = EC_FLASH_PROTECT_ALL_NOW | EC_FLASH_PROTECT_ALL_AT_BOOT;
if (!mdev) {
VBDEBUG("%s: no cros_ec device\n", __func__);
return VBERROR_UNKNOWN;
}
/* Update protection */
if (cros_ec_flash_protect(mdev, mask, protect ? mask : 0, &resp) < 0)
return VBERROR_UNKNOWN;
if (!protect) {
/* If protection is still enabled, need reboot */
if (resp.flags & EC_FLASH_PROTECT_ALL_NOW)
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
return VBERROR_SUCCESS;
}
/*
* If write protect and ro-at-boot aren't both asserted, don't expect
* protection enabled.
*/
if ((~resp.flags) & (EC_FLASH_PROTECT_GPIO_ASSERTED |
EC_FLASH_PROTECT_RO_AT_BOOT))
return VBERROR_SUCCESS;
/* If flash is protected now, success */
if (resp.flags & EC_FLASH_PROTECT_ALL_NOW)
return VBERROR_SUCCESS;
/* If RW will be protected at boot but not now, need a reboot */
if (resp.flags & EC_FLASH_PROTECT_ALL_AT_BOOT)
return VBERROR_EC_REBOOT_TO_RO_REQUIRED;
/* Otherwise, it's an error */
return VBERROR_UNKNOWN;
}
VbError_t VbExEcUpdateImage(int devidx, enum VbSelectFirmware_t select,
const uint8_t *image, int image_size)
{
struct cros_ec_dev *mdev = board_get_cros_ec_dev();
int rv;
if (devidx != 0 || select == VB_SELECT_FIRMWARE_READONLY)
return VBERROR_UNKNOWN;
if (!mdev) {
VBDEBUG("%s: no cros_ec device\n", __func__);
return VBERROR_UNKNOWN;
}
rv = ec_protect_rw(0);
if (rv == VBERROR_EC_REBOOT_TO_RO_REQUIRED)
return rv;
else if (rv != VBERROR_SUCCESS)
return rv;
rv = cros_ec_flash_update_rw(mdev, image, image_size);
return rv == 0 ? VBERROR_SUCCESS : VBERROR_UNKNOWN;
}
VbError_t VbExEcProtect(int devidx, enum VbSelectFirmware_t select)
{
if (devidx != 0 || select == VB_SELECT_FIRMWARE_READONLY)
return VBERROR_UNKNOWN;
return ec_protect_rw(1);
}
VbError_t VbExEcEnteringMode(int devidx, enum VbEcBootMode_t mode)
{
struct cros_ec_dev *mdev = board_get_cros_ec_dev();
if (devidx != 0)
return VBERROR_UNKNOWN;
if (!mdev) {
VBDEBUG("%s: no cros_ec device\n", __func__);
return VBERROR_UNKNOWN;
}
return cros_ec_entering_mode(mdev, mode);
}
VbError_t VbExEcGetExpectedImage(int devidx, enum VbSelectFirmware_t select,
const uint8_t **image, int *image_size)
{
struct fmap_firmware_entry *fw_entry;
struct twostop_fmap fmap;
uint32_t offset;
firmware_storage_t file;
uint8_t *buf;
int size;
*image = NULL;
*image_size = 0;
if (devidx != 0 || select == VB_SELECT_FIRMWARE_READONLY)
return VBERROR_UNKNOWN;
cros_fdtdec_flashmap(gd->fdt_blob, &fmap);
switch (select) {
case VB_SELECT_FIRMWARE_A:
fw_entry = &fmap.readwrite_a;
break;
case VB_SELECT_FIRMWARE_B:
fw_entry = &fmap.readwrite_b;
break;
default:
VBDEBUG("Unrecognized EC firmware requested.\n");
return VBERROR_UNKNOWN;
}
if (firmware_storage_open(&file)) {
VBDEBUG("Failed to open firmware storage.\n");
return VBERROR_UNKNOWN;
}
offset = fw_entry->ec_rw.offset;
size = fw_entry->ec_rw.used;
VBDEBUG("EC-RW image offset %#x used %#x.\n", offset, size);
/* Sanity-check; we don't expect EC images > 1MB */
if (size <= 0 || size > 0x100000) {
VBDEBUG("EC image has bogus size.\n");
return VBERROR_UNKNOWN;
}
/*
* Note: this leaks memory each call, since we don't track the pointer
* and the caller isn't expecting to free it.
*/
buf = malloc(size);
if (!buf) {
VBDEBUG("Failed to allocate space for the EC RW image.\n");
return VBERROR_UNKNOWN;
}
if (file.read(&file, offset, size, buf)) {
free(buf);
VBDEBUG("Failed to read the EC from flash.\n");
return VBERROR_UNKNOWN;
}
file.close(&file);
/* Corrupt a byte if requested */
if (corrupt_offset >= 0 && corrupt_offset < size)
buf[corrupt_offset] = corrupt_byte;
*image = buf;
*image_size = size;
return VBERROR_SUCCESS;
}
VbError_t VbExEcGetExpectedImageHash(int devidx, enum VbSelectFirmware_t select,
const uint8_t **hash, int *hash_size)
{
struct twostop_fmap fmap;
struct fmap_entry *ec;
*hash = NULL;
if (devidx != 0 || select == VB_SELECT_FIRMWARE_READONLY)
return VBERROR_UNKNOWN;
/* TODO: Decode the flashmap once and reuse throughout. */
cros_fdtdec_flashmap(gd->fdt_blob, &fmap);
switch (select) {
case VB_SELECT_FIRMWARE_A:
ec = &fmap.readwrite_a.ec_rw;
break;
case VB_SELECT_FIRMWARE_B:
ec = &fmap.readwrite_b.ec_rw;
break;
default:
return VBERROR_UNKNOWN;
}
if (!ec->hash) {
VBDEBUG("Failed to read EC hash.\n");
return VBERROR_UNKNOWN;
}
*hash = ec->hash;
*hash_size = ec->hash_size;
return VBERROR_SUCCESS;
}
/* Wait 3 seconds after software sync for EC to clear the limit power flag. */
#define LIMIT_POWER_WAIT_TIMEOUT 3000
/* Check the limit power flag every 50 ms while waiting. */
#define LIMIT_POWER_POLL_SLEEP 50
VbError_t VbExEcVbootDone(int in_recovery)
{
struct cros_ec_dev *mdev = board_get_cros_ec_dev();
int limit_power;
int limit_power_wait_time = 0;
int message_printed = 0;
/* Ensure we have enough power to continue booting */
while (1) {
if (cros_ec_read_limit_power_request(mdev, &limit_power)) {
VBDEBUG("Failed to check EC limit power flag.\n");
return VBERROR_UNKNOWN;
}
/*
* Do not wait for the limit power flag to be cleared in
* recovery mode since we didn't just sysjump.
*/
if (!limit_power || in_recovery ||
limit_power_wait_time > LIMIT_POWER_WAIT_TIMEOUT)
break;
if (!message_printed) {
VBDEBUG("Waiting for EC to clear limit power flag.\n");
message_printed = 1;
}
mdelay(LIMIT_POWER_POLL_SLEEP);
limit_power_wait_time += LIMIT_POWER_POLL_SLEEP;
}
if (limit_power) {
VBDEBUG("EC requests limited power usage. Request shutdown.\n");
return VBERROR_SHUTDOWN_REQUESTED;
}
return VBERROR_SUCCESS;
}
VbError_t VbExEcBatteryCutOff(void)
{
VBDEBUG("EC battery cut-off not supported.\n");
return VBERROR_UNKNOWN;
}