blob: 27b41fb7ad10627d142643fe1118390005e1cf79 [file] [log] [blame] [edit]
// SPDX-License-Identifier: BSD-3-Clause
/*
* Copyright 2019 The Fuchsia Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*
*/
#include "libabr.h"
#include <stdbool.h>
#include <stddef.h>
#define MIN_PAGE_SIZE 4096
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
static void abr_data_init(ABRData *data)
{
abr_memset(data, '\0', sizeof(ABRData));
abr_memcpy(data->magic, ABR_MAGIC, ABR_MAGIC_LEN);
data->version_major = ABR_MAJOR_VERSION;
data->version_minor = ABR_MINOR_VERSION;
data->slots[0].priority = ABR_MAX_PRIORITY;
data->slots[0].tries_remaining = ABR_MAX_TRIES_REMAINING;
data->slots[0].successful_boot = 0;
data->slots[1].priority = ABR_MAX_PRIORITY - 1;
data->slots[1].tries_remaining = ABR_MAX_TRIES_REMAINING;
data->slots[1].successful_boot = 0;
}
static bool slot_is_bootable(ABRSlotData *slot)
{
return (slot->priority > 0) &&
(slot->successful_boot || (slot->tries_remaining > 0));
}
static void slot_set_unbootable(ABRSlotData *slot)
{
slot->priority = 0;
slot->tries_remaining = 0;
slot->successful_boot = 0;
}
/* Ensure all unbootable and/or illegal states are marked as the
* canonical 'unbootable' state, e.g. priority=0, tries_remaining=0,
* and successful_boot=0.
*/
static void slot_normalize(ABRSlotData *slot)
{
if (slot->priority > 0) {
if (slot->tries_remaining == 0 && !slot->successful_boot) {
/* We've exhausted all tries -> unbootable. */
slot_set_unbootable(slot);
}
if (slot->tries_remaining > 0 && slot->successful_boot) {
/* Illegal state - abr_mark_slot_successful() will clear
* tries_remaining when setting successful_boot.
* Reset to default state.
*/
slot->tries_remaining = ABR_MAX_TRIES_REMAINING;
slot->successful_boot = 0;
/* having two slots with high priority will not cause an error */
slot->priority = ABR_MAX_PRIORITY;
}
} else {
slot_set_unbootable(slot);
}
}
static ABRResult save_metadata(ABROps *abr_ops, ABRData *data)
{
unsigned char serialized[MIN_PAGE_SIZE] = { 0 };
ABRResult ret;
size_t size = sizeof(ABRData);
abr_debug("Writing A/B metadata to disk.\n");
ret = abr_convert_data_to_buffer(data, serialized, size);
/* abr_convert_data_to_buffer fails only if buffer size is less
* than sizeof(ABRData). This situation should not happen here.
*/
abr_assert(ret == ABR_RESULT_OK);
abr_assert(abr_ops->write_abr_metadata_buffer);
ret =
abr_ops->write_abr_metadata_buffer(serialized, sizeof(serialized));
if (ret != ABR_RESULT_OK)
abr_error("Error writing A/B metadata.\n");
return ret;
}
/* Helper function to load A/B/R metadata - returns ABR_RESULT_OK on
* success, error code otherwise.
*/
static ABRResult load_metadata(ABROps *abr_ops,
ABRData *abr_data, ABRData *abr_data_orig)
{
unsigned char buf[MIN_PAGE_SIZE];
ABRResult ret;
size_t size = sizeof(buf);
abr_assert(abr_ops->read_abr_metadata_buffer);
ret = abr_ops->read_abr_metadata_buffer(buf, &size);
if (ret != ABR_RESULT_OK) {
abr_error("I/O error while loading A/B metadata buffer.\n");
return ret;
}
if (size != sizeof(buf)) {
abr_error("Wrong size of A/B/R metadata buffer.\n");
return ABR_RESULT_ERROR_IO;
}
if (abr_convert_buffer_to_data_and_verify(buf,
sizeof(ABRData),
abr_data) != ABR_RESULT_OK) {
abr_error("Error validating A/B metadata from disk.\n");
abr_data_init(abr_data);
abr_error("Resetting and writing new A/B metadata to disk.\n");
ret = save_metadata(abr_ops, abr_data);
if (ret != ABR_RESULT_OK)
return ret;
}
*abr_data_orig = *abr_data;
/* Ensure data is normalized, e.g. illegal states will be marked as
* unbootable and all unbootable states are represented with
* (priority=0, tries_remaining=0, successful_boot=0).
*/
slot_normalize(&abr_data->slots[0]);
slot_normalize(&abr_data->slots[1]);
return ABR_RESULT_OK;
}
/* Writes A/B/R metadata to disk only if it has changed - returns
* ABR_RESULT_OK on success, error code otherwise.
*/
static ABRResult save_metadata_if_changed(ABROps *abr_ops,
ABRData *abr_data,
ABRData *abr_data_orig)
{
if (abr_safe_memcmp(abr_data, abr_data_orig, sizeof(ABRData)) == 0)
return ABR_RESULT_OK;
return save_metadata(abr_ops, abr_data);
}
/* ==================== public API =================== */
const char *abr_get_slot_name_by_index(unsigned int index)
{
static const char * const slot_suffixes[] = { "_a", "_b", "_r" };
if (index >= ARRAY_SIZE(slot_suffixes)) {
abr_error("Invalid slot index\n");
return NULL;
}
return slot_suffixes[index];
}
unsigned int abr_get_boot_slot(ABROps *abr_ops, bool update_metadata,
bool *is_successful)
{
ABRData abr_data, abr_data_orig;
ABRResult ret;
unsigned int slot_idx_to_boot = ABR_R_SLOT_IDX;
if (is_successful)
*is_successful = false;
ret = load_metadata(abr_ops, &abr_data, &abr_data_orig);
if (ret != ABR_RESULT_OK) {
abr_error
("Failed to load metadata. Switching to 'recovery' mode.\n");
return ABR_R_SLOT_IDX;
}
// oneshot recovery boot has the highest priority
if (abr_data.oneshot_recovery_boot) {
abr_data.oneshot_recovery_boot = 0;
save_metadata(abr_ops, &abr_data);
return ABR_R_SLOT_IDX;
}
if (slot_is_bootable(&abr_data.slots[0])) {
if (is_successful)
*is_successful = true;
slot_idx_to_boot = 0;
if (slot_is_bootable(&abr_data.slots[1]) &&
abr_data.slots[1].priority > abr_data.slots[0].priority) {
slot_idx_to_boot = 1;
}
} else if (slot_is_bootable(&abr_data.slots[1])) {
if (is_successful)
*is_successful = true;
slot_idx_to_boot = 1;
}
if (update_metadata) {
/* ... and decrement tries remaining, if applicable.
* Note: no needs to check if tries_remainig > 0, because
* a slot will be marked as unbootable in `slot_normalize'
* if tries_remainig value is 0
*/
if (slot_idx_to_boot != ABR_R_SLOT_IDX &&
!abr_data.slots[slot_idx_to_boot].successful_boot) {
abr_data.slots[slot_idx_to_boot].tries_remaining--;
if (is_successful)
*is_successful = false;
save_metadata(abr_ops, &abr_data);
}
}
return slot_idx_to_boot;
}
ABRResult abr_mark_slot_active(ABROps *abr_ops, unsigned int slot_number)
{
ABRData abr_data, abr_data_orig;
unsigned int other_slot_number;
ABRResult ret;
if (slot_number > ABR_R_SLOT_IDX) {
abr_error("Wrong slot number.\n");
return ABR_RESULT_ERROR_INVALID_DATA;
}
if (slot_number == ABR_R_SLOT_IDX)
/* do nothing. R slot does not have metadata */
return ABR_RESULT_OK;
ret = load_metadata(abr_ops, &abr_data, &abr_data_orig);
if (ret != ABR_RESULT_OK)
return ret;
/* Make requested slot top priority, unsuccessful, and with max tries. */
abr_data.slots[slot_number].priority = ABR_MAX_PRIORITY;
abr_data.slots[slot_number].tries_remaining = ABR_MAX_TRIES_REMAINING;
abr_data.slots[slot_number].successful_boot = 0;
/* Ensure other slot doesn't have as high a priority. */
other_slot_number = 1 - slot_number;
if (abr_data.slots[other_slot_number].priority == ABR_MAX_PRIORITY) {
abr_data.slots[other_slot_number].priority =
ABR_MAX_PRIORITY - 1;
}
ret = save_metadata_if_changed(abr_ops, &abr_data, &abr_data_orig);
return ret;
}
ABRResult abr_mark_slot_unbootable(ABROps *abr_ops, unsigned int slot_number)
{
ABRData abr_data, abr_data_orig;
ABRResult ret;
if (slot_number > ABR_R_SLOT_IDX) {
abr_error("Wrong slot number.\n");
return ABR_RESULT_ERROR_INVALID_DATA;
}
if (slot_number == ABR_R_SLOT_IDX)
/* do nothing. R slot does not have metadata */
return ABR_RESULT_OK;
ret = load_metadata(abr_ops, &abr_data, &abr_data_orig);
if (ret != ABR_RESULT_OK)
return ret;
slot_set_unbootable(&abr_data.slots[slot_number]);
ret = save_metadata_if_changed(abr_ops, &abr_data, &abr_data_orig);
return ret;
}
ABRResult abr_mark_slot_successful(ABROps *abr_ops, unsigned int slot_number)
{
ABRData abr_data, abr_data_orig;
ABRResult ret;
if (slot_number > ABR_R_SLOT_IDX) {
abr_error("Wrong slot number.\n");
return ABR_RESULT_ERROR_INVALID_DATA;
}
if (slot_number == ABR_R_SLOT_IDX)
/* Do nothing. The R slot does not have metadata */
return ABR_RESULT_OK;
ret = load_metadata(abr_ops, &abr_data, &abr_data_orig);
if (ret != ABR_RESULT_OK)
return ret;
if (!slot_is_bootable(&abr_data.slots[slot_number])) {
abr_error("Cannot mark unbootable slot as successful.\n");
return ABR_RESULT_ERROR_INVALID_DATA;
}
abr_data.slots[slot_number].tries_remaining = 0;
abr_data.slots[slot_number].successful_boot = 1;
ret = save_metadata_if_changed(abr_ops, &abr_data, &abr_data_orig);
return ret;
}
ABRResult abr_get_slot_info(ABROps *abr_ops,
unsigned int slot_number, ABRSlotInfo *info)
{
ABRData abr_data, abr_data_orig;
ABRResult ret;
if (!info) {
abr_error("|info| is NULL.\n");
return ABR_RESULT_ERROR_INVALID_DATA;
}
abr_memset(info, 0, sizeof(ABRSlotInfo));
if (slot_number > ABR_R_SLOT_IDX) {
abr_error("Wrong slot number.\n");
return ABR_RESULT_ERROR_INVALID_DATA;
}
if (slot_number == ABR_R_SLOT_IDX) {
/* assume that R slot is always OK */
info->valid = true;
info->successful_boot = true;
info->retry_count = 0;
return ABR_RESULT_OK;
}
ret = load_metadata(abr_ops, &abr_data, &abr_data_orig);
if (ret != ABR_RESULT_OK)
return ret;
info->valid = slot_is_bootable(&abr_data.slots[slot_number]);
info->successful_boot = abr_data.slots[slot_number].successful_boot;
info->retry_count = abr_data.slots[slot_number].tries_remaining;
return ABR_RESULT_OK;
}
ABRResult abr_convert_buffer_to_data_and_verify(const unsigned char *src,
size_t size, ABRData *dest)
{
ABRData *data = (ABRData *)src;
if (size != sizeof(ABRData)) {
abr_error("Wrong buffer size.\n");
return ABR_RESULT_ERROR_INVALID_DATA;
}
/* Ensure magic is correct. */
if (abr_safe_memcmp(data->magic, ABR_MAGIC, ABR_MAGIC_LEN) != 0) {
abr_error("Magic is incorrect.\n");
return ABR_RESULT_ERROR_INVALID_DATA;
}
abr_memcpy(dest, data, sizeof(ABRData));
dest->crc32 = abr_be32toh(dest->crc32);
/* Ensure we don't attempt to access any fields if the major version
* is not supported.
*/
if (dest->version_major > ABR_MAJOR_VERSION) {
abr_error("No support for given major version.\n");
return ABR_RESULT_ERROR_UNSUPPORTED_VERSION;
}
/* Bail if CRC32 doesn't match. */
if (dest->crc32 !=
abr_crc32((const uint8_t *)dest,
sizeof(ABRData) - sizeof(uint32_t))) {
abr_error("CRC32 does not match.\n");
return ABR_RESULT_ERROR_INVALID_DATA;
}
return ABR_RESULT_OK;
}
ABRResult abr_convert_data_to_buffer(const ABRData *src,
unsigned char *dest, size_t size)
{
ABRData *dest_data = (ABRData *)dest;
if (size != sizeof(ABRData)) {
abr_error("Wrong buffer size.\n");
return ABR_RESULT_ERROR_INVALID_DATA;
}
abr_memcpy(dest, src, sizeof(ABRData));
dest_data->crc32 =
abr_htobe32(abr_crc32(dest, sizeof(ABRData) - sizeof(uint32_t)));
return ABR_RESULT_OK;
}