blob: 4ca757c0067b33bb1180b57c60a964712a12b14b [file] [log] [blame]
/*
* Copyright 2015 Google Inc.
*
* See file CREDITS for list of people who contributed to this
* project.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#include <assert.h>
#include <libpayload.h>
#include <stdint.h>
#include <vboot_nvstorage.h>
#include "base/gpt.h"
#include "base/init_funcs.h"
#include "boot/bcb.h"
#include "drivers/power/power.h"
#include "vboot/stages.h"
#define MAX_CMD_LENGTH 32
#define MAX_REC_LENGTH 768
struct bcb_data {
BlockDev *bdev;
size_t part_addr;
size_t bcb_size_lba;
size_t cmd_size_lba;
} bcb_data;
static uint16_t part_name[MAX_CMD_LENGTH];
static size_t part_name_len;
static uint8_t override_priority;
struct bcb {
char cmd[MAX_CMD_LENGTH];
char status[32];
char rec[MAX_REC_LENGTH];
char stage[32];
char reserved[224];
} bcb_buffer;
/* If there is any failure in BCB handling, reboot into rec mode. */
static void bcb_fail(void)
{
vboot_update_recovery(VBNV_RECOVERY_RW_BCB_ERROR);
cold_reboot();
}
static BlockDev *bcb_find_bdev_from_ctrlr(BlockDevCtrlr *bdev_ctrlr)
{
BlockDev *bdev;
ListNode *devs;
int count = get_all_bdevs(BLOCKDEV_FIXED, &devs);
if (count == 0)
bcb_fail();
list_for_each(bdev, *devs, list_node) {
if (bdev_ctrlr->ops.is_bdev_owned(&bdev_ctrlr->ops,
bdev)) {
return bdev;
}
}
/* Should never reach here. */
bcb_fail();
/* Added to make compiler happy. */
return NULL;
}
static void bcb_init_data(void)
{
/* Early return, if already initialized. */
if (bcb_data.bdev)
return;
BlockDevCtrlr *bdev_ctrlr = bcb_board_bdev_ctrlr();
if (bdev_ctrlr == NULL)
bcb_fail();
bcb_data.bdev = bcb_find_bdev_from_ctrlr(bdev_ctrlr);
/* Allocate GPT data. */
GptData *gpt = alloc_gpt(bcb_data.bdev);
if (gpt == NULL)
bcb_fail();
/* Read GPT entries. */
GptHeader *header = (GptHeader *)gpt->primary_header;
GptEntry *entries = (GptEntry *)gpt->primary_entries;
GptEntry *e;
int i;
/* BCB-partition label: "MSC" */
const uint16_t lbl_required[] = {'M','S','C'};
/* Identify the entry for BCB partition. */
for (i = 0, e = entries; i < header->number_of_entries; i++, e++) {
if (memcmp(e->name, lbl_required, sizeof(lbl_required)) == 0)
break;
}
/* If BCB is not found, reboot into rec mode. */
if (i == header->number_of_entries)
bcb_fail();
/* Ensure bcb partition is big enough to hold bcb data. */
size_t part_size_lba;
bcb_data.part_addr = e->starting_lba;
part_size_lba = GptGetEntrySizeLba(e);
bcb_data.cmd_size_lba = ALIGN_UP(MAX_CMD_LENGTH, gpt->sector_bytes);
bcb_data.cmd_size_lba /= gpt->sector_bytes;
bcb_data.bcb_size_lba = sizeof(struct bcb) / gpt->sector_bytes;
if (bcb_data.bcb_size_lba > part_size_lba)
bcb_fail();
/* Cleanup GPT data */
free_gpt(bcb_data.bdev, gpt);
}
/* Copy argument to cmd which is the name of the partition to be booted. */
static void bcb_copy_part_name(char *arg, size_t len)
{
int i;
/* arg is a NULL-terminated string containing partition label. */
part_name_len = strnlen(arg, len);
/*
* This arg will be compared against GPT labels to check which partition
* needs priority override. Since, GPT labels are stored in uint16_t[],
* copy arg to uint16_t[] to enable use of memcmp in
* bcb_override_priority().
*/
for (i = 0; i < part_name_len; i++)
part_name[i] = arg[i];
override_priority = 1;
}
static void bcb_reboot_bootloader(char *arg, size_t len)
{
vboot_update_recovery(VBNV_RECOVERY_BCB_USER_MODE);
}
/* Post-cmd handle flags. */
enum {
CLEAR_CMD = (1 << 0),
REBOOT_DEVICE = (1 << 1),
FLAGS_NONE = 0,
};
void bcb_handle_command(void)
{
/* If device is in recovery, no need to initialize BCB. */
if (vboot_in_recovery())
return;
/* Initialize data required to read/write BCB. */
bcb_init_data();
/* Read BCB partition. */
BlockDevOps *ops = &bcb_data.bdev->ops;
if (ops->read(ops, bcb_data.part_addr, bcb_data.bcb_size_lba,
(void *)&bcb_buffer) != bcb_data.bcb_size_lba)
bcb_fail();
/* List of expected cmds. */
static const struct {
/* cmd string */
char name[MAX_CMD_LENGTH];
/* callback to handle this cmd */
void (*fnptr)(char *arg, size_t len);
/* flags: indicate actions after handling cmd */
uint32_t post_handle_flags;
} cmd_table[] = {
{"boot-", bcb_copy_part_name, FLAGS_NONE},
{"bootonce-", bcb_copy_part_name, CLEAR_CMD},
{"boot-bootloader", bcb_reboot_bootloader, REBOOT_DEVICE},
{"bootonce-bootloader", bcb_reboot_bootloader,
CLEAR_CMD | REBOOT_DEVICE},
};
/* Pick the cmd with highest match len. */
int match_len = -1, match_index = -1;
int i;
for (i = 0; i < ARRAY_SIZE(cmd_table); i++) {
int len = strlen(cmd_table[i].name);
if ((strncmp(bcb_buffer.cmd, cmd_table[i].name, len) == 0)
&& (len > match_len)) {
match_len = len;
match_index = i;
}
}
/* No cmd in BCB. */
if (match_index == -1)
return;
/* Callback for the cmd, if present. */
if (cmd_table[match_index].fnptr)
cmd_table[match_index].fnptr(bcb_buffer.cmd + match_len,
MAX_CMD_LENGTH - match_len);
/* Write back to BCB, if required. */
if (cmd_table[match_index].post_handle_flags & CLEAR_CMD) {
memset(bcb_buffer.cmd, 0, MAX_CMD_LENGTH);
if (ops->write(ops, bcb_data.part_addr, bcb_data.cmd_size_lba,
(void *)&bcb_buffer.cmd)
!= bcb_data.cmd_size_lba)
bcb_fail();
}
/* Reboot device, if required. */
if (cmd_table[match_index].post_handle_flags & REBOOT_DEVICE)
cold_reboot();
}
uint8_t bcb_override_priority(const uint16_t *name)
{
/*
* If BCB partition requests boot into a particular partition,
* override its priority.
*/
if (override_priority &&
(memcmp(part_name, name, part_name_len) == 0))
return 1;
return 0;
}
void bcb_request_recovery(const char *cmd, const char *rec)
{
/* Ensure correct lengths for cmd and rec. */
assert(strlen(cmd) < MAX_CMD_LENGTH);
assert(strlen(rec) < MAX_REC_LENGTH);
/* Initialize data required to write BCB. */
bcb_init_data();
/* Fill in bcb buffer. */
memset(&bcb_buffer, 0, sizeof(bcb_buffer));
strcpy(bcb_buffer.cmd, cmd);
strcpy(bcb_buffer.rec, rec);
/* Write bcb buffer to bdev. */
BlockDevOps *ops = &bcb_data.bdev->ops;
if (ops->write(ops, bcb_data.part_addr, bcb_data.bcb_size_lba,
(void *)&bcb_buffer) != bcb_data.bcb_size_lba)
bcb_fail();
}