blob: 7508343a8beaf3ef8b5846684bfabe567b93dafe [file] [log] [blame]
/*
* Copyright (c) 2012 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.
*/
/*
* Debug commands for testing Verify Boot Export APIs that are implemented by
* firmware and exported to vboot_reference. Some of the tests are automatic
* but some of them are manual.
*/
/* #define TEST_BATTERY */
#ifdef TEST_BATTERY
#include <battery.h>
#endif
#include <common.h>
#include <command.h>
#include <cros_ec.h>
#include <ec_commands.h>
#include <fdtdec.h>
#include <i2c.h>
#include <malloc.h>
#include <asm/gpio.h>
#include <power/tps65090_pmic.h>
#include <cros/common.h>
#include <cros/firmware_storage.h>
DECLARE_GLOBAL_DATA_PTR;
static int do_cros_test_i2c(cmd_tbl_t *cmdtp, int flag,
int argc, char * const argv[])
{
#ifdef CONFIG_POWER_TPS65090
int fet_id = 2;
int ret = 0;
int pass;
# ifdef TEST_BATTERY
struct battery_info base_info, info;
if (smartbat_init()) {
printf("Cannot init smart battery\n");
return 1;
}
# endif
if (tps65090_init()) {
printf("Cannot init tps65090\n");
return 1;
}
# ifdef TEST_BATTERY
if (smartbat_get_info(&base_info)) {
printf("Cannot get battery info\n");
return 1;
}
# endif
/* Generate continuous i2c traffic and make sure all is well */
for (pass = 0; pass < 100000; pass++) {
/* Do some things on the bus, first with TPSCHROME */
ret = tps65090_fet_enable(fet_id);
if (ret)
break;
if (!tps65090_fet_is_enabled(fet_id)) {
ret = -1;
break;
}
ret = tps65090_fet_disable(fet_id);
if (ret)
break;
if (tps65090_fet_is_enabled(fet_id)) {
ret = -1;
break;
}
# ifdef TEST_BATTERY
/* Now have a look at the battery */
if (smartbat_get_info(&info)) {
printf("Pass %d: Battery read error\n", pass);
continue;
}
if (0 == memcmp(&base_info, &info, sizeof(info))) {
puts("Battery info comparison failed\n");
ret = -1;
break;
}
# endif
if (ctrlc()) {
puts("Test terminated by ctrl-c\n");
break;
}
/* mdelay(1); */
}
if (ret)
printf("Error detected on pass %d\n", pass);
#endif /* CONFIG_POWER_TPS65090 */
return 0;
}
#if defined(CONFIG_DRIVER_S3C24X0_I2C) && defined(CONFIG_EXYNOS5250) \
&& defined(CONFIG_CMD_GPIO)
#include <asm/arch/pinmux.h>
static int setup_i2c(int *nodep)
{
int node;
int err;
node = fdt_path_offset(gd->fdt_blob, "i2c4");
if (node < 0) {
printf("Error: Cannot find fdt node\n");
return -1;
}
if (board_i2c_claim_bus(node)) {
printf("Error: Cannot claim bus\n");
return -1;
}
err = gpio_request(EXYNOS5_GPIO_A20, "i2creset");
err |= gpio_request(EXYNOS5_GPIO_A21, "i2creset");
err |= gpio_direction_output(EXYNOS5_GPIO_A20, 0);
err |= gpio_direction_output(EXYNOS5_GPIO_A21, 0);
if (err) {
printf("Error: Could not set up GPIOs\n");
return -1;
}
*nodep = node;
return 0;
}
static int restore_i2c(int node)
{
int err;
err = gpio_direction_output(EXYNOS5_GPIO_A20, 1);
err |= gpio_direction_output(EXYNOS5_GPIO_A21, 1);
if (err) {
printf("Error: Could not restore GPIOs\n");
return -1;
}
if (i2c_reset_port_fdt(gd->fdt_blob, node)) {
printf("Error: Could not reset I2C after operation\n");
return -1;
}
if (exynos_pinmux_config(PERIPH_ID_I2C4, 0)) {
printf("Error: Could not restore I2C\n");
return -1;
}
board_i2c_release_bus(node);
return 0;
}
static int do_cros_test_i2creset(cmd_tbl_t *cmdtp, int flag,
int argc, char * const argv[])
{
int node;
printf("Holding I2C bus 4 low for 15 seconds\n");
if (setup_i2c(&node))
return 1;
mdelay(15 * 1000);
if (restore_i2c(node))
return 1;
printf(" - done, I2C restored\n");
return 0;
}
/**
* Return a 1-bit pseudo-random number related to the supplied seed
*
* @param seed Seed value (any integer)
* @return pseudo-random number (0 or 1)
*/
static int get_fiddle_value(int seed)
{
int value = 0;
int i;
for (i = 0; i < 32; i++)
value ^= (seed >> i) & 1;
return value;
}
static int do_cros_test_i2cfiddle(cmd_tbl_t *cmdtp, int flag,
int argc, char * const argv[])
{
int node, i;
printf("Generate random transitions on I2C bus 4\n");
if (setup_i2c(&node))
return 1;
for (i = 0; i < 100; i++) {
gpio_set_value(EXYNOS5_GPIO_A20, get_fiddle_value(i));
udelay(1);
gpio_set_value(EXYNOS5_GPIO_A21, get_fiddle_value(100 - i));
udelay(1);
}
if (restore_i2c(node))
return 1;
printf(" - done, I2C restored\n");
return 0;
}
#endif
#ifdef CONFIG_CROS_EC
struct ssync_info {
struct cros_ec_dev *dev; /* cros_ec device */
firmware_storage_t file; /* Firmware storage access */
struct twostop_fmap fmap; /* Chrome OS Flash map */
uint8_t *old_data; /* Buffer to hold old EC image */
uint8_t *new_data; /* Buffer to hold new EC image */
int force; /* Force flashing even if the same */
int verify; /* Verify EC image after writing */
struct fdt_cros_ec ec; /* EC configuration */
};
static int ensure_region_writable(struct cros_ec_dev *dev,
enum ec_flash_region region)
{
enum ec_current_image image;
enum ec_flash_region want_region;
enum ec_reboot_cmd cmd;
int ret;
ret = cros_ec_read_current_image(dev, &image);
if (ret) {
debug("%s: Could not read image\n", __func__);
return ret;
}
if (image == EC_IMAGE_UNKNOWN) {
debug("%s: Unknown current image\n", __func__);
return -1;
}
/*
* Work out which flash region we want to be in. If we are writing
* to RO, then the EC needs to be running from RW, and vice versa.
*/
want_region = region == EC_FLASH_REGION_RO ?
EC_FLASH_REGION_RW : EC_FLASH_REGION_RO;
/* If we are not already in the right image, change it */
if (!!(want_region == EC_FLASH_REGION_RO) !=
!!(image == EC_IMAGE_RO)) {
if (want_region == EC_FLASH_REGION_RO)
cmd = EC_REBOOT_JUMP_RO;
else
cmd = EC_REBOOT_JUMP_RW;
ret = cros_ec_reboot(dev, cmd, 0);
if (ret) {
debug("%s: Could not boot into image\n", __func__);
return ret;
}
}
/* We are now in the right image */
return 0;
}
/*
* Remove FMAP signature pollution from EC image
*
* crosbug.com/p/13143
*
* cros_bundle_firmware has a workaround which pollutes the fmap
* signature so that the RO EC binary doesn't include a valid fmap.
* This would confuse flashrom when it writes the firmware to SPI
* flash.
*
* As a result, we must fix this up here. This can be removed when
* flashrom is no longer involved with EC update (i.e. a pure
* software sync is done).
*
* This only needs to be done for the RO image, but we do it for both since
* the __fMAP__ signature doesn't exist in RW, so this code will have no
* effect.
*/
static void unpollute_image(uint8_t *data, int size)
{
uint32_t *ptr, *end;
end = (uint32_t *)(data + size);
for (ptr = (uint32_t *)data; ptr < end - 1; ptr++) {
/* Look for __fMAP__ and change to __FMAP__ */
if (ptr[0] == 0x4d665f5f && ptr[1] == 0x5f5f5041) {
*ptr = 0x4d465f5f;
break;
}
}
}
static int do_ssync(struct ssync_info *ssync, uint32_t offset, uint32_t size,
struct fmap_entry *entry)
{
uint32_t reset, base;
int ret;
if (ssync->file.read(&ssync->file, entry->offset,
entry->length, ssync->new_data)) {
debug("%s: Cannot read firmware storage\n", __func__);
return -1;
}
unpollute_image(ssync->new_data, entry->length);
/*
* Do a sanity check on the image. This uses information about EC
* internals, which is nasty, but OTOH it is very useful to get a
* check that we are not writing garbage to the EC. This can easily
* happen if the firmware in the SPI flash is not the same as that
* running (e.g. USB download is used).
*
* The second word of the image is the reset vector, and it should
* point at least 100 bytes into the image, since the vector table
* and version info precede the init code pointed to by the reset
* vector. Otherwise, on link, where base=0, you could end up
* thinking /dev/zero is a valid EC-RO image.
*
* (Currently on snow, reset vector points to 0x15c; on link, it
* points to 0x278).
*/
reset = *(uint32_t *)(ssync->new_data + 4);
base = ssync->ec.flash.offset + offset;
if (reset < base + 100 || reset >= base + size) {
printf("Invalid EC image (reset vector %#08x)\n",
reset);
return -1;
}
if (!ssync->force) {
puts("read, ");
ret = cros_ec_flash_read(ssync->dev, ssync->old_data,
offset, size);
if (ret) {
debug("%s: Cannot read flash\n", __func__);
return ret;
}
if (0 == memcmp(ssync->old_data, ssync->new_data, size)) {
printf("same, skipping update, ");
return 0;
}
}
puts("erase, ");
ret = cros_ec_flash_erase(ssync->dev, offset, size);
if (ret) {
debug("%s: Cannot erase flash\n", __func__);
return ret;
}
puts("write, ");
ret = cros_ec_flash_write(ssync->dev, ssync->new_data, offset, size);
if (ret) {
debug("%s: Cannot write flash\n", __func__);
return ret;
}
if (ssync->verify) {
puts("verify, ");
ret = cros_ec_flash_read(ssync->dev, ssync->old_data, offset,
size);
if (ret) {
debug("%s: Cannot write flash\n", __func__);
return ret;
}
if (memcmp(ssync->old_data, ssync->new_data, size)) {
debug("%s: Cannot verify flash\n", __func__);
return ret;
}
}
return 0;
}
static int process_region(struct ssync_info *ssync,
enum ec_flash_region region)
{
struct fmap_entry *entry;
uint32_t offset, size;
int ret;
printf("Flashing %s EC image: ",
region == EC_FLASH_REGION_RW ? "RW" : "RO");
ret = ensure_region_writable(ssync->dev, region);
if (ret)
return ret;
ret = cros_ec_flash_offset(ssync->dev, region, &offset, &size);
if (ret) {
debug("%s: Cannot read flash offset for region %d\n",
__func__, region);
return ret;
}
ssync->new_data = malloc(size);
ssync->old_data = malloc(size);
if (!ssync->new_data || !ssync->old_data) {
debug("%s: Cannot malloc %d bytes\n", __func__,
size * 2);
return -1;
}
/*
* Clear to the erase value to make it flash friendly. If we don't
* know the erase value, assume 0xff. Getting this wrong won't break
* things, but might take longer.
*/
memset(ssync->new_data, ssync->ec.flash_erase_value, size);
entry = region == EC_FLASH_REGION_RW ?
&ssync->fmap.readonly.ec_rw :
&ssync->fmap.readonly.ec_ro;
ret = do_ssync(ssync, offset, size, entry);
if (ret)
return -1;
free(ssync->new_data);
free(ssync->old_data);
puts("done\n");
return 0;
}
int cros_test_swsync(struct cros_ec_dev *dev, int region_mask, int force,
int verify)
{
struct ssync_info ssync;
ulong start, duration;
int ret = 0, reboot_ret;
ssync.dev = dev;
ssync.force = force;
ssync.verify = verify;
assert(dev);
if (cros_fdtdec_flashmap(gd->fdt_blob, &ssync.fmap)) {
printf("Cannot read Chrome OS flashmap information\n");
return 1;
}
if (cros_ec_decode_ec_flash(gd->fdt_blob, &ssync.ec)) {
printf("Cannot read Chrome OS EC information\n");
return 1;
}
if (firmware_storage_open(&ssync.file)) {
debug("%s: Cannot open firmware storage\n", __func__);
return 1;
}
start = get_timer(0);
/* Do RW first, since it might not be bootable, but RO must be */
if (region_mask & (1 << EC_FLASH_REGION_RW))
ret = process_region(&ssync, EC_FLASH_REGION_RW);
if (!ret && (region_mask & (1 << EC_FLASH_REGION_RO)))
ret = process_region(&ssync, EC_FLASH_REGION_RO);
ssync.file.close(&ssync.file);
if (ret)
printf("\nSoftware sync failed with error %d\n", ret);
/* Ensure we exit in the RO image */
reboot_ret = cros_ec_reboot(dev, EC_REBOOT_JUMP_RO, 0);
if (reboot_ret)
debug("%s: Could not boot into RO image\n", __func__);
if (ret)
return ret;
duration = get_timer(start);
printf("Full software sync completed in %lu.%lus\n", duration / 1000,
duration % 1000);
return 0;
}
static int do_cros_test_swsync(cmd_tbl_t *cmdtp, int flag,
int argc, char * const argv[])
{
int region_mask = -1;
struct cros_ec_dev *dev;
int region, force, verify;
int ret;
force = verify = 0;
dev = board_get_cros_ec_dev();
if (!dev) {
printf("No cros_ec device available\n");
return 1;
}
argc--;
argv++;
if (argc > 0 && *argv[0] == '-') {
const char *arg;
for (arg = argv[0] + 1; *arg; arg++) {
switch (*arg) {
case 'f':
force = 1;
break;
case 'v':
verify = 1;
break;
default:
return CMD_RET_USAGE;
}
}
argc--;
argv++;
}
if (argc > 0) {
region = cros_ec_decode_region(argc, argv);
if (region == -1) {
printf("Invalid region\n");
return 1;
}
region_mask = 1 << region;
}
ret = cros_test_swsync(dev, region_mask, force, verify);
if (ret)
return 1;
return 0;
}
#endif /* CONFIG_CROS_EC */
static int do_cros_test_corruptec(cmd_tbl_t *cmdtp, int flag,
int argc, char * const argv[])
{
char *endp;
int offset;
int byte;
if (argc < 3)
return CMD_RET_USAGE;
offset = simple_strtoul(argv[1], &endp, 16);
if (*argv[1] == 0 || *endp != 0)
return CMD_RET_USAGE;
byte = simple_strtoul(argv[2], &endp, 16);
if (*argv[2] == 0 || *endp != 0)
return CMD_RET_USAGE;
printf("Setting byte at offset %#x to %#02x\n", offset, byte);
cros_ec_set_corrupt_image(offset, byte);
return 0;
}
static int do_cros_test_all(cmd_tbl_t *cmdtp, int flag,
int argc, char * const argv[])
{
int ret = 0;
ret |= do_cros_test_i2c(cmdtp, flag, argc, argv);
if (!ret)
printf("All tests passed\n");
return ret;
}
U_BOOT_SUBCMD_START(cmd_cros_test_sub)
U_BOOT_CMD_MKENT(i2c, 0, 1, do_cros_test_i2c, "", "")
#if defined(CONFIG_DRIVER_S3C24X0_I2C) && defined(CONFIG_EXYNOS5250) \
&& defined(CONFIG_CMD_GPIO)
U_BOOT_CMD_MKENT(i2creset, 0, 1, do_cros_test_i2creset, "", "")
U_BOOT_CMD_MKENT(i2cfiddle, 0, 1, do_cros_test_i2cfiddle, "", "")
#endif
#ifdef CONFIG_CROS_EC
U_BOOT_CMD_MKENT(swsync, 0, 1, do_cros_test_swsync, "", "")
#endif
U_BOOT_CMD_MKENT(corruptec, 0, 1, do_cros_test_corruptec, "", "")
U_BOOT_CMD_MKENT(all, 0, 1, do_cros_test_all, "", "")
U_BOOT_SUBCMD_END
static int do_cros_test(cmd_tbl_t *cmdtp, int flag,
int argc, char * const argv[])
{
cmd_tbl_t *c;
if (argc < 2)
return cmd_usage(cmdtp);
argc--;
argv++;
c = find_cmd_tbl(argv[0], &cmd_cros_test_sub[0],
ARRAY_SIZE(cmd_cros_test_sub));
if (c)
return c->cmd(c, flag, argc, argv);
else
return cmd_usage(cmdtp);
}
U_BOOT_CMD(cros_test, CONFIG_SYS_MAXARGS, 1, do_cros_test,
"Perform tests for Chrome OS",
"all Run all tests\n"
"i2c Test i2c link with EC, and arbitration\n"
"i2creset Try to reset i2c bus by holding clk, data low for 15s\n"
"i2cfiddle Try to break TPSCHROME or the battery on i2c\n"
"swsync [-f] [ro|rw] Flash the EC (read-only/read-write/both)\n"
" -f Force update even if the same\n"
"corruptec <offset> <byte> Corrupt a single byte of the EC image"
"during verified boot"
);