Remove signature-based vboot support

Superseded by EC software sync (hash-based).

Sig-based vboot was correctly implemented, but ended up being too slow
to be useful given the limited processing power of the EC chips, and
we also couldn't come up with a manageable way to handle A/B
autoupdate of signed EC firmware.

This change and an associated vboot_reference change shrinks the EC
binary by ~2KB.

BUG=chrome-os-partner:11232
TEST=build link,snow; boot link and check that 'hash' command still works.

Change-Id: I3f03ae2d0a4030977826980d6ec5613181e154c2
Signed-off-by: Randall Spangler <rspangler@chromium.org>
Reviewed-on: https://gerrit.chromium.org/gerrit/29496
Reviewed-by: Bill Richardson <wfrichar@chromium.org>
This commit is contained in:
Randall Spangler
2012-08-07 15:43:57 -07:00
committed by Gerrit
parent 29cbe51663
commit 45cd8463a3
15 changed files with 6 additions and 581 deletions

View File

@@ -16,8 +16,6 @@
/* Optional features */
#define CONFIG_CONSOLE_CMDHELP
#define CONFIG_SYSTEM_UNLOCKED /* Allow dangerous commands */
#define CONFIG_VBOOT
/* FIXME: This got broken. #define CONFIG_VBOOT_SIG */
#ifndef __ASSEMBLER__

View File

@@ -24,7 +24,6 @@
/* compute RW firmware hash at startup */
#define CONFIG_VBOOT
#define CONFIG_VBOOT_HASH
/* Allow dangerous commands all the time, since we don't have a write protect
* switch. */

View File

@@ -31,7 +31,6 @@
#define CONFIG_TMP006
#define CONFIG_USB_CHARGE
#define CONFIG_VBOOT
#define CONFIG_VBOOT_HASH
#ifndef __ASSEMBLER__

View File

@@ -28,7 +28,6 @@
/* compute RW firmware hash at startup */
#define CONFIG_VBOOT
#define CONFIG_VBOOT_HASH
#ifndef __ASSEMBLER__

View File

@@ -59,36 +59,11 @@
#define CONFIG_SECTION_RW_OFF (CONFIG_SECTION_RO_OFF \
+ CONFIG_SECTION_RO_SIZE)
#ifdef CONFIG_VBOOT_SIG
/*
* The top of each section will hold the vboot stuff, since the firmware vector
* table has to go at the start. The root key will fit in 2K, but the vblocks
* need 4K.
*/
#define CONFIG_VBOOT_ROOTKEY_SIZE 0x800
#define CONFIG_VBLOCK_SIZE 0x1000
/* RO: firmware (+ FMAP), root keys */
#define CONFIG_FW_RO_OFF CONFIG_SECTION_RO_OFF
#define CONFIG_FW_RO_SIZE (CONFIG_SECTION_RO_SIZE \
- CONFIG_VBOOT_ROOTKEY_SIZE)
#define CONFIG_VBOOT_ROOTKEY_OFF (CONFIG_FW_RO_OFF + CONFIG_FW_RO_SIZE)
/* RW: firmware, vblock */
#define CONFIG_FW_RW_OFF CONFIG_SECTION_RW_OFF
#define CONFIG_FW_RW_SIZE (CONFIG_SECTION_RW_SIZE \
- CONFIG_VBLOCK_SIZE)
#define CONFIG_VBLOCK_RW_OFF (CONFIG_FW_RW_OFF + CONFIG_FW_RW_SIZE)
#else /* CONFIG_VBOOT_SIG */
#define CONFIG_FW_RO_OFF CONFIG_SECTION_RO_OFF
#define CONFIG_FW_RO_SIZE CONFIG_SECTION_RO_SIZE
#define CONFIG_FW_RW_OFF CONFIG_SECTION_RW_OFF
#define CONFIG_FW_RW_SIZE CONFIG_SECTION_RW_SIZE
#endif /* CONFIG_VBOOT_SIG */
/****************************************************************************/
/* Customize the build */

View File

@@ -239,11 +239,6 @@ void keyboard_scan_clear_boot_key(void)
#endif
}
int keyboard_scan_recovery_pressed(void)
{
return boot_key_value == BOOT_KEY_ESC ? 1 : 0;
}
int keyboard_scan_init(void)
{
/* Configure GPIO */
@@ -279,7 +274,7 @@ int keyboard_scan_init(void)
}
/* Trigger event if recovery key was pressed */
if (keyboard_scan_recovery_pressed())
if (boot_key_value == BOOT_KEY_ESC)
host_set_single_event(EC_HOST_EVENT_KEYBOARD_RECOVERY);
}

View File

@@ -40,28 +40,15 @@ VBOOT_DEVKEYS?=/usr/share/vboot/devkeys
CFLAGS_$(CONFIG_VBOOT)+= -DCHROMEOS_ENVIRONMENT -DCHROMEOS_EC
# CFLAGS_$(CONFIG_VBOOT)+= -DVBOOT_DEBUG
common-$(CONFIG_VBOOT)+=vboot.o vboot_stub.o
common-$(CONFIG_VBOOT_HASH)+=vboot_hash.o
common-$(CONFIG_VBOOT_SIG)+=vboot_sig.o
common-$(CONFIG_VBOOT)+=vboot_stub.o vboot_hash.o
includes-$(CONFIG_VBOOT)+= \
$(VBOOT_SOURCE)/include \
$(VBOOT_SOURCE)/lib/include \
$(VBOOT_SOURCE)/lib/cryptolib/include
dirs-$(CONFIG_VBOOT)+= \
vboot/lib vboot/lib/cryptolib
dirs-$(CONFIG_VBOOT)+=vboot/lib vboot/lib/cryptolib
vboot-$(CONFIG_VBOOT)+= \
lib/cryptolib/padding.o \
lib/cryptolib/sha_utility.o \
lib/cryptolib/sha256.o
vboot-$(CONFIG_VBOOT_SIG)+= \
lib/vboot_common.o \
lib/utility.o \
lib/cryptolib/rsa_utility.o \
lib/cryptolib/rsa.o \
lib/stateful_util.o
sign-$(CONFIG_VBOOT_SIG)+=sign_image

View File

@@ -45,11 +45,7 @@ typedef struct _FmapAreaHeader {
uint16_t area_flags;
} __packed FmapAreaHeader;
#ifdef CONFIG_VBOOT_SIG
#define NUM_EC_FMAP_AREAS (7 + 3)
#else
#define NUM_EC_FMAP_AREAS 7
#endif
const struct _ec_fmap {
FmapHeader header;
@@ -112,16 +108,8 @@ const struct _ec_fmap {
.area_size = CONFIG_SECTION_RO_SIZE,
.area_flags = FMAP_AREA_STATIC | FMAP_AREA_RO,
},
#ifdef CONFIG_VBOOT_SIG
{
.area_name = "ROOT_KEY",
.area_offset = CONFIG_VBOOT_ROOTKEY_OFF,
.area_size = CONFIG_VBOOT_ROOTKEY_SIZE,
.area_flags = FMAP_AREA_STATIC | FMAP_AREA_RO,
},
#endif
/* RW Firmware */
/* RW Firmware */
{
/* The range of RW firmware to be auto-updated. */
.area_name = "EC_RW",
@@ -139,20 +127,5 @@ const struct _ec_fmap {
.area_size = sizeof(version_data.version),
.area_flags = FMAP_AREA_STATIC,
},
#ifdef CONFIG_VBOOT_SIG
{
.area_name = "FW_MAIN",
.area_offset = CONFIG_FW_RW_OFF,
.area_size = CONFIG_FW_RW_SIZE,
.area_flags = FMAP_AREA_STATIC,
},
{
.area_name = "VBLOCK",
.area_offset = CONFIG_VBLOCK_RW_OFF,
.area_size = CONFIG_VBLOCK_SIZE,
.area_flags = FMAP_AREA_STATIC,
},
#endif
}
};

View File

@@ -20,7 +20,6 @@
#include "task.h"
#include "timer.h"
#include "uart.h"
#include "vboot.h"
#include "watchdog.h"
int main(void)
@@ -115,25 +114,6 @@ int main(void)
keyboard_scan_init();
#endif
#ifdef CONFIG_VBOOT_SIG
/*
* Verified boot signature check. This may jump to another image, which
* will need to reconfigure / reinitialize the system, so as little as
* possible should be done above this step.
*
* Note that steps above here may be done TWICE per boot, once in the
* RO image and once in the RW image.
*/
vboot_check_signature();
/*
* If system is locked, disable system jumps now that vboot has had its
* chance to jump to a RW image.
*/
if (system_is_locked())
system_disable_jump();
#endif
/*
* Initialize other driver modules. These can occur in any order.
* Non-driver modules with tasks do their inits from their task

View File

@@ -1,46 +0,0 @@
/* 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.
*/
/* Verified boot module for Chrome EC */
#include "common.h"
#include "console.h"
#include "host_command.h"
#include "system.h"
#include "vboot.h"
/* Console output macros */
#define CPUTS(outstr) cputs(CC_VBOOT, outstr)
#define CPRINTF(format, args...) cprintf(CC_VBOOT, format, ## args)
/****************************************************************************/
/* Host commands */
static int host_cmd_vboot(struct host_cmd_handler_args *args)
{
const struct ec_params_vboot_cmd *p = args->params;
struct ec_params_vboot_cmd *r = args->response;
uint8_t v;
switch (p->in.cmd) {
case VBOOT_CMD_GET_FLAGS:
v = VBOOT_FLAGS_IMAGE_MASK & system_get_image_copy();
r->out.get_flags.val = v;
args->response_size = sizeof(r);
break;
case VBOOT_CMD_SET_FLAGS:
v = p->in.set_flags.val;
break;
default:
CPRINTF("[%T LB bad cmd 0x%x]\n", p->in.cmd);
return EC_RES_INVALID_PARAM;
}
return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_VBOOT_CMD,
host_cmd_vboot,
EC_VER_MASK(0));

View File

@@ -1,163 +0,0 @@
/* 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.
*/
/* Verified boot module for Chrome EC */
#include "board.h"
#include "config.h"
#include "console.h"
#include "cryptolib.h"
#include "gpio.h"
#include "system.h"
#include "timer.h"
#include "util.h"
#include "vboot.h"
#include "vboot_api.h"
#include "vboot_common.h"
#include "vboot_struct.h"
#include "watchdog.h"
/* Console output macros */
#define CPUTS(outstr) cputs(CC_VBOOT, outstr)
#define CPRINTF(format, args...) cprintf(CC_VBOOT, format, ## args)
enum howgood {
IMAGE_IS_BAD,
IMAGE_IS_GOOD,
IMAGE_IS_GOOD_BUT_USE_RO_ANYWAY,
};
static enum howgood good_image(uint8_t *key_data,
uint8_t *vblock_data, uint32_t vblock_size,
uint8_t *fv_data, uint32_t fv_size) {
VbPublicKey *sign_key;
VbKeyBlockHeader *key_block;
VbECPreambleHeader *preamble;
uint32_t now = 0;
RSAPublicKey *rsa;
key_block = (VbKeyBlockHeader *)vblock_data;
sign_key = (VbPublicKey *)key_data;
watchdog_reload();
if (0 != KeyBlockVerify(key_block, vblock_size, sign_key, 0)) {
CPRINTF("[Error verifying key block]\n");
return IMAGE_IS_BAD;
}
now += key_block->key_block_size;
rsa = PublicKeyToRSA(&key_block->data_key);
if (!rsa) {
CPRINTF("[Error parsing data key]\n");
return IMAGE_IS_BAD;
}
watchdog_reload();
preamble = (VbECPreambleHeader *)(vblock_data + now);
if (0 != VerifyECPreamble(preamble, vblock_size - now, rsa)) {
CPRINTF("[Error verifying preamble]\n");
RSAPublicKeyFree(rsa);
return IMAGE_IS_BAD;
}
if (preamble->flags & VB_FIRMWARE_PREAMBLE_USE_RO_NORMAL) {
CPRINTF("[Flags says USE_RO_NORMAL]\n");
RSAPublicKeyFree(rsa);
return IMAGE_IS_GOOD_BUT_USE_RO_ANYWAY;
}
watchdog_reload();
if (0 != EqualData(fv_data, fv_size, &preamble->body_digest, rsa)) {
CPRINTF("Error verifying firmware body]\n");
RSAPublicKeyFree(rsa);
return IMAGE_IS_BAD;
}
RSAPublicKeyFree(rsa);
watchdog_reload();
CPRINTF("[Verified!]\n");
return IMAGE_IS_GOOD;
}
/* Might I want to jump to one of the RW images? */
static int maybe_jump_to_other_image(void)
{
/* We'll only jump to another image if we're currently in RO */
if (system_get_image_copy() != SYSTEM_IMAGE_RO)
return 0;
#ifdef CONFIG_TASK_KEYSCAN
/* Don't jump if recovery requested */
if (keyboard_scan_recovery_pressed()) {
CPUTS("[Vboot staying in RO because recovery key pressed]\n");
return 0;
}
#endif
/*
* Don't jump if we're in RO becuase we jumped there (this keeps us
* from jumping to RO only to jump right back).
*/
if (system_jumped_to_this_image())
return 0;
#if !defined(CHIP_stm32)
/*
* TODO: (crosbug.com/p/8572) Daisy and Snow don't define a GPIO for
* the recovery signal from servo, so we can't check it. BDS uses the
* DOWN button.
*/
if (gpio_get_level(GPIO_RECOVERYn) == 0) {
CPUTS("[Vboot staying in RO due to recovery signal]\n");
return 0;
}
#endif
/* Okay, we might want to jump to a RW image. */
return 1;
}
int vboot_check_signature(void)
{
enum howgood r;
timestamp_t ts1, ts2;
CPRINTF("[%T Vboot init]\n");
if (!maybe_jump_to_other_image())
return EC_SUCCESS;
CPRINTF("[%T Vboot check RW image...]\n");
ts1 = get_time();
r = good_image((uint8_t *)CONFIG_VBOOT_ROOTKEY_OFF,
(uint8_t *)CONFIG_VBLOCK_RW_OFF, CONFIG_VBLOCK_SIZE,
(uint8_t *)CONFIG_FW_RW_OFF, CONFIG_FW_RW_SIZE);
ts2 = get_time();
CPRINTF("[%T Vboot result=%d, elapsed time=%ld us]\n",
r, ts2.val - ts1.val);
switch (r) {
case IMAGE_IS_GOOD:
CPRINTF("[RW image verified]\n");
system_run_image_copy(SYSTEM_IMAGE_RW);
CPRINTF("[ERROR: Unable to jump to RW image]\n");
goto bad;
case IMAGE_IS_GOOD_BUT_USE_RO_ANYWAY:
CPRINTF("[RW image verified]\n");
CPRINTF("[Staying in RO mode]\n");
return EC_SUCCESS;
default:
CPRINTF("[RW image is invalid]\n");
}
bad:
CPRINTF("[Staying in RO mode]\n");
CPRINTF("[FIXME: How to trigger recovery mode?]\n");
return EC_ERROR_UNKNOWN;
}

View File

@@ -6,24 +6,9 @@
/* Functions needed by vboot library */
#define _STUB_IMPLEMENTATION_
#include "console.h"
#include "shared_mem.h"
#include "util.h"
#include "utility.h"
/* Console output macros */
#define CPUTS(outstr) cputs(CC_VBOOT, outstr)
#define CPRINTF(format, args...) cprintf(CC_VBOOT, format, ## args)
#if 0 /* change this to debug memory usage */
#define DPRINTF CPRINTF
#else
#define DPRINTF(...)
#endif
/****************************************************************************/
void *Memcpy(void *dest, const void *src, uint64_t n)
{
return memcpy(dest, src, (size_t)n);
@@ -33,137 +18,3 @@ void *Memset(void *d, const uint8_t c, uint64_t n)
{
return memset(d, c, n);
}
int Memcmp(const void *src1, const void *src2, size_t n)
{
size_t i;
const uint8_t *a = src1;
const uint8_t *b = src2;
for (i = 0; i < n; i++) {
if (*a != *b)
return (*a < *b) ? -1 : 1;
a++;
b++;
}
return 0;
}
/****************************************************************************/
/* The vboot_api library requires some dynamic memory, but we don't have
* malloc/free. Instead we have one chunk of shared RAM that we can gain
* exclusive access to for a time. We'll have to experiment with various
* algorithms to see what works best. This algorithm allocates and
* reuses blocks, but never actually frees anything until all memory has been
* reclaimed. */
/* Since we only need this stuff to boot, don't waste run-time .bss */
static struct {
int bucket_size; /* total RAM available */
uint8_t *out_base; /* malloc from here */
int out_count; /* number of active mallocs */
int out_size; /* high-water mark */
/* We have a limited number of active mallocs. We never free, but we
* do reuse slots. */
#define MAX_SLOTS 8
struct {
int in_use; /* is this slot active? */
void *ptr; /* starts here */
size_t size; /* how big */
} slots[MAX_SLOTS];
} *bucket;
void *VbExMalloc(size_t size)
{
int i, j;
void *ptr = 0;
if (!bucket) {
i = shared_mem_size();
if (EC_SUCCESS != shared_mem_acquire(i, 1, (char **)&bucket)) {
CPRINTF("FAILED at %s:%d\n", __FILE__, __LINE__);
ASSERT(0);
}
Memset(bucket, 0, sizeof(*bucket));
bucket->bucket_size = i;
bucket->out_base = (uint8_t *)(bucket + 1);
bucket->out_size = sizeof(*bucket);
DPRINTF("grab the bucket: at 0x%x, size 0x%x\n",
bucket, bucket->bucket_size);
}
if (size % 8) {
size_t tmp = (size + 8) & ~0x7ULL;
DPRINTF(" %d -> %d\n", size, tmp);
ASSERT(tmp >= size);
size = tmp;
}
for (i = 0; i < MAX_SLOTS; i++) {
if (!bucket->slots[i].in_use) {
/* Found an empty one, but reuse the same size if one
* already exists. */
for (j = i; j < MAX_SLOTS; j++) {
if (!bucket->slots[j].in_use &&
size == bucket->slots[j].size) {
/* empty AND same size */
bucket->slots[j].in_use = 1;
ptr = bucket->slots[j].ptr;
DPRINTF(" = %d (%d)\n", j, size);
goto out;
}
}
/* no exact matches, must allocate a new chunk */
ptr = bucket->out_base + bucket->out_size;
bucket->out_size += size;
bucket->slots[i].in_use = 1;
bucket->slots[i].ptr = ptr;
bucket->slots[i].size = size;
DPRINTF(" + %d (%d)\n", i, size);
goto out;
}
}
CPRINTF("FAILED: no empty slots (%d/%d)\n", i, MAX_SLOTS);
ASSERT(0);
out:
bucket->out_count++;
if (bucket->out_size >= bucket->bucket_size) {
CPRINTF("FAILED: out of memory (%d/%d)\n",
bucket->out_size, bucket->bucket_size);
ASSERT(0);
}
return ptr;
}
void VbExFree(void *ptr)
{
int i;
for (i = 0; i < MAX_SLOTS; i++) {
if (ptr == bucket->slots[i].ptr) {
bucket->slots[i].in_use = 0;
DPRINTF(" - %d (%d)\n", i, bucket->slots[i].size);
break;
}
}
if (MAX_SLOTS == i) {
CPRINTF("FAILED: can't find ptr %x!\n", ptr);
ASSERT(0);
}
bucket->out_count--;
if (!bucket->out_count) {
DPRINTF("dump the bucket: max used = %d\n", bucket->out_size);
shared_mem_release(bucket);
bucket = 0;
}
}

View File

@@ -623,34 +623,9 @@ struct ec_params_lightbar_cmd {
/* Verified boot commands */
/*
* Verified boot uber-command. Details still evolving. Like the lightbar
* command above, this takes sub-commands.
* Note: command code 0x29 version 0 was VBOOT_CMD in Link EVT; it may be
* reused for other purposes with version > 0.
*/
#define EC_CMD_VBOOT_CMD 0x29
struct ec_params_vboot_cmd {
union {
union {
uint8_t cmd;
struct {
uint8_t cmd;
/* no inputs */
} get_flags;
struct {
uint8_t cmd;
uint8_t val;
} set_flags;
} in;
union {
struct {
uint8_t val;
} get_flags;
struct {
/* no outputs */
} set_flags;
} out;
};
} __packed;
/* Verified boot hash command */
#define EC_CMD_VBOOT_HASH 0x2A

View File

@@ -1,37 +0,0 @@
/* 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.
*/
/* Verified boot module for Chrome EC */
#ifndef __CROS_EC_VBOOT_H
#define __CROS_EC_VBOOT_H
#include "common.h"
/*
* Check verified boot signatures, and jump to one of the RW images if
* necessary.
*/
int vboot_check_signature(void);
/* Initialize the module. */
int vboot_init(void);
/* These are the vboot commands available via LPC. */
enum vboot_command {
VBOOT_CMD_GET_FLAGS,
VBOOT_CMD_SET_FLAGS,
VBOOT_NUM_CMDS,
};
/*
* These are the flags transferred across LPC. At the moment, only the devmode
* flag can be set, and only because it's faked. Ultimately this functionality
* will be moved elsewhere.
*/
#define VBOOT_FLAGS_IMAGE_MASK 0x03 /* enum system_image_copy_t */
#define VBOOT_FLAGS_UNUSED 0x04 /* was fake dev-mode bit */
#endif /* __CROS_EC_VBOOT_H */

View File

@@ -14,7 +14,6 @@
#include "battery.h"
#include "comm-host.h"
#include "lightbar.h"
#include "vboot.h"
/* Handy tricks */
#define BUILD_ASSERT(cond) ((void)sizeof(char[1 - 2*!(cond)]))
@@ -117,8 +116,6 @@ const char help_str[] =
" Set the threshold temperature value for thermal engine.\n"
" usbchargemode <port> <mode>\n"
" Set USB charging mode\n"
" vboot [VAL]\n"
" Get or set vboot flags\n"
" version\n"
" Prints EC version\n"
" wireless <mask>\n"
@@ -1165,61 +1162,6 @@ static int cmd_lightbar(int argc, char **argv)
}
/* This needs to match the values defined in vboot.h. I'd like to
* define this in one and only one place, but I can't think of a good way to do
* that without adding bunch of complexity. This will do for now.
*/
static const struct {
uint8_t insize;
uint8_t outsize;
} vb_command_paramcount[] = {
{ sizeof(((struct ec_params_vboot_cmd *)0)->in.get_flags),
sizeof(((struct ec_params_vboot_cmd *)0)->out.get_flags) },
{ sizeof(((struct ec_params_vboot_cmd *)0)->in.set_flags),
sizeof(((struct ec_params_vboot_cmd *)0)->out.set_flags) },
};
static int cmd_vboot(int argc, char **argv)
{
int rv;
uint8_t v;
char *e;
struct ec_params_vboot_cmd param;
if (argc == 1) { /* no args = get */
param.in.cmd = VBOOT_CMD_GET_FLAGS;
rv = ec_command(EC_CMD_VBOOT_CMD, 0,
&param,
vb_command_paramcount[param.in.cmd].insize,
&param,
vb_command_paramcount[param.in.cmd].outsize);
if (rv < 0)
return rv;
v = param.out.get_flags.val;
printf("0x%02x image=%s\n", v,
image_names[VBOOT_FLAGS_IMAGE_MASK & v]);
return 0;
}
/* else args => set values */
v = strtoul(argv[1], &e, 16) & 0xff;
if (e && *e) {
fprintf(stderr, "Bad value\n");
return -1;
}
param.in.cmd = VBOOT_CMD_SET_FLAGS;
param.in.set_flags.val = v;
rv = ec_command(EC_CMD_VBOOT_CMD, 0,
&param,
vb_command_paramcount[param.in.cmd].insize,
&param,
vb_command_paramcount[param.in.cmd].outsize);
return (rv < 0 ? rv : 0);
}
int cmd_usb_charge_set_mode(int argc, char *argv[])
{
struct ec_params_usb_charge_set_mode p;
@@ -2125,7 +2067,6 @@ const struct command commands[] = {
{"i2cread", cmd_i2c_read},
{"i2cwrite", cmd_i2c_write},
{"lightbar", cmd_lightbar},
{"vboot", cmd_vboot},
{"pstoreinfo", cmd_pstore_info},
{"pstoreread", cmd_pstore_read},
{"pstorewrite", cmd_pstore_write},
@@ -2153,7 +2094,6 @@ int main(int argc, char *argv[])
const struct command *cmd;
BUILD_ASSERT(ARRAY_SIZE(lb_command_paramcount) == LIGHTBAR_NUM_CMDS);
BUILD_ASSERT(ARRAY_SIZE(vb_command_paramcount) == VBOOT_NUM_CMDS);
if (argc < 2 || !strcasecmp(argv[1], "-?") ||
!strcasecmp(argv[1], "help")) {