blob: d97d81b2b16b568022ad9d6904fd81b0368baf1a [file] [log] [blame]
/*
* Copyright (c) 2019 The Fuchsia Authors
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <common.h>
#include <abr/abr.h>
#include <abr/ops.h>
#include <amlogic/aml_image.h>
#include <zircon/boot/image.h>
#include <zircon_uboot/partition.h>
#include <zircon_uboot/vboot.h>
#include <zircon_uboot/zircon.h>
#define ABR_OFFSET 0
static bool read_abr_metadata(void *context, size_t size, uint8_t *buffer)
{
assert(buffer != NULL);
zircon_partition *part =
zircon_get_partition(zircon_abr_partition_name);
if (!part) {
fprintf(stderr, "%s partition not found.\n",
zircon_abr_partition_name);
return false;
}
if (part->read(part, ABR_OFFSET, buffer, size)) {
fprintf(stderr, "failed to read A/B/R metadata.\n");
zircon_free_partition(part);
return false;
}
zircon_free_partition(part);
return true;
}
static bool write_abr_metadata(void *context, const uint8_t *buffer,
size_t size)
{
assert(buffer != NULL);
zircon_partition *part =
zircon_get_partition(zircon_abr_partition_name);
if (!part) {
fprintf(stderr, "%s partition not found.\n",
zircon_abr_partition_name);
return false;
}
if (part->write(part, ABR_OFFSET, buffer, size)) {
fprintf(stderr, "failed to read A/B/R metadata.\n");
zircon_free_partition(part);
return false;
}
zircon_free_partition(part);
return true;
}
static const AbrOps abr_ops = {
.read_abr_metadata = read_abr_metadata,
.write_abr_metadata = write_abr_metadata,
};
const AbrOps *zircon_abr_ops(void)
{
return &abr_ops;
}
const char *zircon_slot_idx_to_part_name(AbrSlotIndex slot_index)
{
switch (slot_index) {
case kAbrSlotIndexA:
return "zircon_a";
case kAbrSlotIndexB:
return "zircon_b";
case kAbrSlotIndexR:
return "zircon_r";
}
printf("Error: invalid slot index %d\n", slot_index);
return "";
}
// Minimum read size needed to verify existence of a ZBI and get length is the
// optional AMLogic signing header (not used by recent builds) plus the first
// ZBI header.
#define ZIRCON_IMAGE_HEADER_BUF_SIZE \
(sizeof(aml_boot_header_t) + sizeof(zbi_header_t))
static int zircon_find_headers(const zircon_partition *part,
bool *out_aml_hdr_found,
aml_boot_header_t *out_aml_hdr,
bool *out_zbi_hdr_found,
zbi_header_t *out_zbi_hdr)
{
unsigned char buf[ZIRCON_IMAGE_HEADER_BUF_SIZE]
__attribute__((aligned(ZBI_ALIGNMENT)));
if (part->read(part, 0, buf, sizeof(buf))) {
return -1;
}
uint64_t zbi_offset = 0;
aml_boot_header_t *aml_hdr = (aml_boot_header_t *)buf;
bool aml_hdr_found = aml_hdr->magic == AML_BOOT_HEADER_MAGIC;
if (out_aml_hdr_found)
*out_aml_hdr_found = aml_hdr_found;
if (aml_hdr_found) {
zbi_offset = sizeof(aml_boot_header_t);
if (out_aml_hdr)
*out_aml_hdr = *aml_hdr;
}
zbi_header_t *zbi_hdr = (zbi_header_t *)(buf + zbi_offset);
// Just check if the header looks like a ZBI container. We'll do a full
// ZBI validity check later once we've loaded the whole image into memory.
bool zbi_hdr_found = (zbi_hdr->type == ZBI_TYPE_CONTAINER &&
zbi_hdr->extra == ZBI_CONTAINER_MAGIC &&
zbi_hdr->magic == ZBI_ITEM_MAGIC);
if (out_zbi_hdr_found)
*out_zbi_hdr_found = zbi_hdr_found;
if (zbi_hdr_found) {
if (out_zbi_hdr)
*out_zbi_hdr = *zbi_hdr;
}
return 0;
}
/**
* zircon_get_img_size() - Gets upper bound size for image in partition
*
* The size is derived from the Amlogic boot header if it exists.
* If not, the first ZBI header is used. If neither header is found,
* the entire partition size is used.
*
* @partition: The zircon partition in which the image resides.
* @size: Set to an upper bound of the full image size, including amlogic
* secure boot headers and ZBI headers.
* @img_offset: Set to image offset.
*
* Return: 0 on success, negative value on failure
*/
int zircon_get_img_size(const zircon_partition *part, uint64_t *size,
size_t *img_offset)
{
bool aml_hdr_found;
aml_boot_header_t aml_hdr;
bool zbi_hdr_found;
zbi_header_t zbi_hdr;
if (zircon_find_headers(part, &aml_hdr_found, &aml_hdr, &zbi_hdr_found,
&zbi_hdr)) {
fprintf(stderr, "Failed to find headers\n");
return -1;
}
// Always set to 0 unless Amlogic boot header found
*img_offset = 0;
// Get an upper bound on image size by using the outermost header of
// the image and falling back on the partition size.
// The Amlogic header is always found first if it exists.
if (aml_hdr_found) {
*size = aml_hdr.img_size + sizeof(aml_boot_header_t);
*img_offset = sizeof(aml_boot_header_t);
printf("AML header found: img_size: %llu\n", *size);
} else if (zbi_hdr_found) {
*size = zbi_hdr.length + sizeof(zbi_header_t);
printf("ZBI header found: img_size: %llu\n", *size);
} else {
*size = part->size;
printf("Headers not found. Using partition size: %llu\n",
*size);
}
return 0;
}
/** zircon_load_kernel() - Loads zircon kernel into specified address.
*
* @loadaddr: The address at which to load the kernel.
* @loadsize: The size of the load buffer.
* @slot_idx: The slot index from which to read the kernel.
* @has_successfully_booted: True if this slot has previously booted successfully.
* @img_offset: Set to the image offset, if known, otherwise, set to 0.
*
* Return: 0 if successful, negative value on failure.
*/
static int zircon_load_kernel(unsigned char *loadaddr, size_t loadsize,
AbrSlotIndex slot_idx,
bool has_successfully_booted, size_t *img_offset)
{
const char *ab_suffix = AbrGetSlotSuffix(slot_idx);
if (ab_suffix == NULL) {
fprintf(stderr, "Invalid slot_idx %d\n", slot_idx);
return -1;
}
const char *part_name = zircon_slot_idx_to_part_name(slot_idx);
printf("ABR: loading kernel from %s...\n", part_name);
zircon_partition *part = zircon_get_partition(part_name);
if (!part) {
fprintf(stderr, "partition not found: %s\n", part_name);
return -1;
}
uint64_t img_size;
if (zircon_get_img_size(part, &img_size, img_offset)) {
fprintf(stderr, "unable to get zircon image size\n");
zircon_free_partition(part);
return -1;
}
if (img_size > loadsize) {
fprintf(stderr, "Image is too large to load (%llu > %zu)\n",
img_size, loadsize);
return -1;
}
if (part->read(part, 0, loadaddr, img_size)) {
fprintf(stderr, "Failed to to read partition\n");
zircon_free_partition(part);
return -1;
}
zbi_header_t *zbi = (zbi_header_t *)(loadaddr + *img_offset);
size_t capacity = loadsize - *img_offset;
if (zircon_vboot_slot_verify(loadaddr, img_size, ab_suffix,
has_successfully_booted, zbi, capacity)) {
fprintf(stderr, "Failed to verify slot: %s\n", ab_suffix);
zircon_free_partition(part);
return -1;
}
printf("Successfully loaded slot: %s\n", ab_suffix);
zircon_free_partition(part);
return 0;
}
int zircon_abr_img_load(unsigned char *loadaddr, size_t loadsize,
bool force_recovery, size_t *img_offset,
AbrSlotIndex *slot)
{
int ret = 0;
AbrSlotIndex cur_slot;
do {
bool has_successfully_booted;
/* check recovery mode */
if (force_recovery) {
cur_slot = kAbrSlotIndexR;
has_successfully_booted = true;
} else {
cur_slot = AbrGetBootSlot(zircon_abr_ops(), true,
&has_successfully_booted);
}
ret = zircon_load_kernel(loadaddr, loadsize, cur_slot,
has_successfully_booted, img_offset);
if (ret) {
fprintf(stderr, "ABR: failed to load slot %d: %s\n",
cur_slot,
zircon_slot_idx_to_part_name(cur_slot));
if (AbrMarkSlotUnbootable(zircon_abr_ops(), cur_slot) !=
kAbrResultOk) {
return -1;
}
continue;
}
} while ((ret != 0) && (cur_slot != kAbrSlotIndexR));
if (ret != 0) {
fprintf(stderr, "Fail to boot: no valid slots\n");
return -1;
}
*slot = cur_slot;
return 0;
}