blob: 700d16a85f55c04c0eb979027295f452297c58a7 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* Implementation of EC callbacks
*
* Copyright 2018 Google LLC
*/
#define LOG_CATEGORY UCLASS_CROS_EC
#include <common.h>
#include <dm.h>
#include <cros_ec.h>
#include <log.h>
#include <malloc.h>
#include <vb2_api.h>
#include <cros/cros_common.h>
#include <cros/fwstore.h>
#include <cros/vboot.h>
#include <cros/vboot_ec.h>
#include <cros/vboot_flag.h>
#include <cros/aux_fw.h>
#include <linux/delay.h>
DECLARE_GLOBAL_DATA_PTR;
int vb2ex_ec_trusted(void)
{
int gpio_ec_in_rw;
int okay;
log_debug("start\n");
/* If we don't have a valid GPIO to read, we can't trust it */
gpio_ec_in_rw = vboot_flag_read_walk(VBOOT_FLAG_EC_IN_RW);
if (gpio_ec_in_rw < 0) {
log_debug("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;
log_debug("value=%d, returning %d\n", gpio_ec_in_rw, okay);
return okay;
}
/**
* ec_get() - Get the EC based on the index
*
* @devidx: EC index (0 for first, 1 for second, etc.)
* @devp: Returns the EC found on success
* @return 0 if OK, -ve on error
*/
static int ec_get(int devidx, struct udevice **devp)
{
struct udevice *dev;
int ret;
ret = uclass_get_device_by_seq(UCLASS_CROS_VBOOT_EC, devidx, &dev);
if (ret) {
log_err("Get EC %d: err=%d\n", devidx, ret);
return VB2_ERROR_UNKNOWN;
}
log_debug("EC devidx=%d,name=%s\n", devidx, dev->name);
*devp = dev;
return 0;
}
vb2_error_t vb2ex_ec_running_rw(int *in_rw)
{
struct udevice *dev;
int ret;
log_debug("start\n");
ret = ec_get(0, &dev);
if (ret)
return log_msg_ret("ec", ret);
ret = vboot_ec_running_rw(dev, in_rw);
if (ret) {
log_err("Failed, err=%d\n", ret);
return VB2_ERROR_UNKNOWN;
}
return VB2_SUCCESS;
}
vb2_error_t vb2ex_ec_jump_to_rw(void)
{
struct udevice *dev;
int ret;
log_debug("start\n");
ret = ec_get(0, &dev);
if (ret)
return log_msg_ret("ec", ret);
ret = vboot_ec_jump_to_rw(dev);
if (ret) {
log_err("Failed, err=%d\n", ret);
return VB2_ERROR_UNKNOWN;
}
return VB2_SUCCESS;
}
vb2_error_t vb2ex_ec_disable_jump(void)
{
struct udevice *dev;
int ret;
log_debug("start\n");
ret = ec_get(0, &dev);
if (ret)
return log_msg_ret("ec", ret);
ret = vboot_ec_disable_jump(dev);
if (ret) {
log_err("Failed, err=%d\n", ret);
return VB2_ERROR_UNKNOWN;
}
return VB2_SUCCESS;
}
vb2_error_t vb2ex_ec_hash_image(enum vb2_firmware_selection select,
const u8 **hashp, int *hash_sizep)
{
struct udevice *dev;
int ret;
log_debug("start\n");
ret = ec_get(0, &dev);
if (ret)
return log_msg_ret("ec", ret);
ret = vboot_ec_hash_image(dev, select, hashp, hash_sizep);
log_debug("ret=%d, hash ptr=%p, hash_size=%x\n", ret, *hashp,
*hash_sizep);
if (ret) {
log_err("Failed, err=%d\n", ret);
return VB2_ERROR_UNKNOWN;
}
return VB2_SUCCESS;
}
/**
* get_firmware_entry() - Find the firmware entry for an EC
*
* @vboot: vboot info
* @devidx: EC index (0 for first, 1 for second, etc.)
* @select: Firmware to select
* @return pointer to the entry containing the EC to use, NULL on error
*/
static struct fmap_entry *get_firmware_entry(struct vboot_info *vboot,
int devidx,
enum vb2_firmware_selection select)
{
struct fmap_section *fw;
struct fmap_ec *ec;
struct fmap_entry *entry;
fw = vboot_is_slot_a(vboot) ? &vboot->fmap.readwrite_a :
&vboot->fmap.readwrite_b;
if (devidx < 0 || devidx >= EC_COUNT) {
log_err("entry not found, slot=%s, devidx=%d, select=%d",
vboot_slot_name(vboot), devidx, select);
return NULL;
}
ec = &fw->ec[devidx];
entry = select == VB_SELECT_FIRMWARE_READONLY ? &ec->ro : &ec->rw;
log_debug("Selected devidx=%d, select=%s\n", devidx,
select == VB_SELECT_FIRMWARE_READONLY ? "ro" : "rw");
log_debug("entry->hash=%p, hash_size=%x\n", entry->hash,
entry->hash_size);
return entry;
}
vb2_error_t ec_get_image(enum vb2_firmware_selection select, struct abuf **bufp)
{
struct vboot_info *vboot = vboot_get();
struct fmap_entry *entry;
struct abuf *buf;
int ret;
log_debug("start\n");
entry = get_firmware_entry(vboot, 0, select);
if (!entry)
return VB2_ERROR_UNKNOWN;
/* Reuse the same image to avoid leaking memory */
buf = &vboot->expected_ec_image;
ret = fwstore_load_image(vboot->fwstore, entry, buf);
if (ret) {
log_err("Cannot locate image: err=%d\n", ret);
return VB2_ERROR_UNKNOWN;
}
*bufp = buf;
return VB2_SUCCESS;
}
vb2_error_t vb2ex_ec_get_expected_image_hash(enum vb2_firmware_selection select,
const u8 **hash, int *hash_size)
{
struct vboot_info *vboot = vboot_get();
struct fmap_entry *entry;
int i;
log_debug("start\n");
entry = get_firmware_entry(vboot, 0, select);
if (!entry) {
log_err("Cannot get firmware entry:select=%d\b", select);
return VB2_ERROR_UNKNOWN;
}
*hash = entry->hash;
*hash_size = entry->hash_size;
log_debug("Expected: ");
for (i = 0; i < entry->hash_size; i++)
log_debug("%02x", entry->hash[i]);
log_debug("\n");
return VB2_SUCCESS;
}
vb2_error_t vb2ex_ec_update_image(enum vb2_firmware_selection select)
{
struct abuf *buf;
struct udevice *dev;
int ret;
log_debug("start\n");
ret = ec_get(0, &dev);
if (ret)
return log_msg_ret("ec", ret);
ret = ec_get_image(select, &buf);
if (ret)
return log_msg_ret("image", ret);
ret = vboot_ec_update_image(dev, select, buf);
if (ret) {
log_err("Failed, err=%d\n", ret);
switch (ret) {
case -EINVAL:
return VB2_ERROR_INVALID_PARAMETER;
case -EPERM:
return VB2_REQUEST_REBOOT_EC_TO_RO;
case -EIO:
default:
return VB2_ERROR_UNKNOWN;
}
}
return VB2_SUCCESS;
}
vb2_error_t vb2ex_ec_protect(enum vb2_firmware_selection select)
{
struct udevice *dev;
int ret;
log_debug("start\n");
ret = ec_get(0, &dev);
if (ret)
return log_msg_ret("ec", ret);
ret = vboot_ec_protect(dev, select);
if (ret) {
log_err("Failed, err=%d\n", ret);
return VB2_ERROR_UNKNOWN;
}
return VB2_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
vb2_error_t vb2ex_ec_vboot_done(struct vb2_context *ctx)
{
struct vboot_info *vboot = ctx_to_vboot(ctx);
struct udevice *dev, *cros_ec;
int limit_power;
int ret;
ret = ec_get(0, &dev);
if (ret)
return log_msg_ret("ec", ret);
cros_ec = dev_get_parent(dev);
log_debug("start\n");
/* Ensure we have enough power to continue booting */
while (1) {
bool message_printed = false;
int limit_power_wait_time = 0;
int ret;
ret = cros_ec_read_limit_power(cros_ec, &limit_power);
if (ret == -ENOSYS) {
limit_power = 0;
} else if (ret) {
log_warning("Failed to check EC limit power flag\n");
return VB2_ERROR_UNKNOWN;
}
/*
* Do not wait for the limit power flag to be cleared in
* recovery mode since we didn't just sysjump.
*/
if (!limit_power || vboot_is_recovery(vboot) ||
limit_power_wait_time > LIMIT_POWER_WAIT_TIMEOUT)
break;
if (!message_printed) {
log_info("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) {
log_info("EC requests limited power usage. Request shutdown\n");
return VB2_REQUEST_SHUTDOWN;
}
bootstage_mark(BOOTSTAMP_VBOOT_EC_DONE);
return VB2_SUCCESS;
}
vb2_error_t vb2ex_ec_battery_cutoff(void)
{
struct udevice *dev = board_get_cros_ec_dev();
int ret;
log_debug("start\n");
if (!dev) {
log_warning("No EC\n");
return VB2_ERROR_UNKNOWN;
}
ret = cros_ec_battery_cutoff(dev, EC_BATTERY_CUTOFF_FLAG_AT_SHUTDOWN);
if (ret) {
log_err("Failed, err=%d\n", ret);
return VB2_ERROR_UNKNOWN;
}
return VB2_SUCCESS;
}