blob: 358a97a52e08f9e0f0cb1fa6a06e8a66659585e9 [file] [log] [blame] [edit]
/* 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 <lib/abr/abr.h>
#include <lib/abr/data.h>
#include <lib/abr/util.h>
/* Initializes |data| with default valid values. Both A/B slots will be marked as bootable, but not
* successful, with a full set of retries. Slot A will be higher priority.
*/
static void abr_data_init(AbrData* data) {
AbrMemset(data, 0, sizeof(*data));
AbrMemcpy(data->magic, kAbrMagic, kAbrMagicLen);
data->version_major = kAbrMajorVersion;
data->version_minor = kAbrMinorVersion;
data->slot_data[0].priority = kAbrMaxPriority;
data->slot_data[0].tries_remaining = kAbrMaxTriesRemaining;
data->slot_data[0].successful_boot = 0;
data->slot_data[1].priority = kAbrMaxPriority - 1;
data->slot_data[1].tries_remaining = kAbrMaxTriesRemaining;
data->slot_data[1].successful_boot = 0;
}
/* Deserializes and validates |size| bytes from |buffer|. On success, |dest| is populated with the
* valid data and kAbrResultOk is returned.
*/
static AbrResult abr_data_deserialize(const uint8_t* buffer, size_t size, AbrData* dest) {
if (size < sizeof(*dest)) {
ABR_ERROR("Wrong serialized data size.\n");
return kAbrResultErrorInvalidData;
}
AbrMemcpy(dest, buffer, sizeof(*dest));
/* Ensure magic is correct. */
if (AbrSafeMemcmp(dest->magic, kAbrMagic, kAbrMagicLen) != 0) {
ABR_ERROR("Magic is incorrect.\n");
return kAbrResultErrorInvalidData;
}
/* Bail if CRC32 doesn't match. */
dest->crc32 = AbrBigEndianToHost(dest->crc32);
if (dest->crc32 != AbrCrc32(dest, sizeof(*dest) - sizeof(uint32_t))) {
ABR_ERROR("CRC32 does not match.\n");
return kAbrResultErrorInvalidData;
}
/* Ensure we don't attempt to access any fields if the major version is not supported. */
if (dest->version_major > kAbrMajorVersion) {
ABR_ERROR("No support for given major version.\n");
return kAbrResultErrorUnsupportedVersion;
}
return kAbrResultOk;
}
/* Updates the checksum and serializes |src| to |dest| which must point to at least sizeof(AbrData)
* bytes.
*/
static void abr_data_serialize(AbrData* src, uint8_t* dest) {
src->crc32 = AbrHostToBigEndian(AbrCrc32(src, sizeof(*src) - sizeof(uint32_t)));
AbrMemcpy(dest, src, sizeof(*src));
}
static bool is_slot_bootable(const AbrSlotData* slot) {
return (slot->priority > 0) && (slot->successful_boot || (slot->tries_remaining > 0));
}
static void set_slot_unbootable(AbrSlotData* slot) {
slot->tries_remaining = 0;
slot->successful_boot = 0;
}
static uint8_t get_normalized_priority(const AbrSlotData* slot) {
return is_slot_bootable(slot) ? slot->priority : 0;
}
static AbrSlotIndex get_active_slot(const AbrData* abr_data) {
uint8_t priority_a = get_normalized_priority(&abr_data->slot_data[kAbrSlotIndexA]);
uint8_t priority_b = get_normalized_priority(&abr_data->slot_data[kAbrSlotIndexB]);
// zero priority means unbootable.
if (priority_b > priority_a) {
return kAbrSlotIndexB;
} else if (priority_a) {
return kAbrSlotIndexA;
}
return kAbrSlotIndexR;
}
static bool is_slot_active(const AbrData* abr_data, AbrSlotIndex slot_index) {
return get_active_slot(abr_data) == slot_index;
}
/* Ensure all unbootable or invalid states are marked as the canonical 'unbootable' state. That is,
* 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. */
set_slot_unbootable(slot);
}
if ((slot->tries_remaining > 0) && slot->successful_boot) {
/* Illegal state - AbrMarkSlotSuccessful() will clear tries_remaining when setting
* successful_boot. Reset to not successful state.
*/
slot->tries_remaining = kAbrMaxTriesRemaining;
slot->successful_boot = 0;
}
if (slot->priority > kAbrMaxPriority) {
slot->priority = kAbrMaxPriority;
}
if (slot->tries_remaining > kAbrMaxTriesRemaining) {
slot->tries_remaining = kAbrMaxTriesRemaining;
}
} else {
set_slot_unbootable(slot);
}
}
/* Saves |abr_data| to persistent storage, overwriting any existing persistent state. */
static AbrResult save_metadata(const AbrOps* abr_ops, AbrData* abr_data) {
uint8_t serialized[sizeof(*abr_data)];
ABR_DEBUG("Writing A/B metadata to disk.\n");
if (abr_ops->write_abr_metadata_custom) {
if (!abr_ops->write_abr_metadata_custom(abr_ops->context, &abr_data->slot_data[0],
&abr_data->slot_data[1],
abr_data->one_shot_recovery_boot)) {
ABR_ERROR("Failed to write metadata.\n");
return kAbrResultErrorIo;
}
} else {
abr_data_serialize(abr_data, serialized);
if (abr_ops->write_abr_metadata == NULL) {
ABR_ERROR("Failed to write metadata (not implemented).\n");
return kAbrResultErrorIo;
}
if (!abr_ops->write_abr_metadata(abr_ops->context, serialized, sizeof(serialized))) {
ABR_ERROR("Failed to write metadata.\n");
return kAbrResultErrorIo;
}
}
return kAbrResultOk;
}
/* Loads |abr_data| from persistent storage and normalizes it, initializing new data if necessary.
* Changes as a result of normalization are not written back to persistent storage but a copy of the
* exact original data from persistent storage is provided in |abr_data_orig| for future use with
* save_metadata_if_changed().
*
* On success, populates abr_data, abr_data_orig, and returns kAbrResultOk. On failure an
* ABR_RESULT_ERROR* error code is returned and the contents of abr_data and abr_data_orig are
* undefined.
*/
static AbrResult load_metadata(const AbrOps* abr_ops, AbrData* abr_data, AbrData* abr_data_orig) {
AbrResult result = kAbrResultOk;
uint8_t serialized[sizeof(*abr_data)];
if (abr_ops->read_abr_metadata == NULL && abr_ops->read_abr_metadata_custom == NULL) {
ABR_ERROR("Failed to read metadata (not implemented).\n");
return kAbrResultErrorIo;
}
if (abr_ops->read_abr_metadata != NULL) {
if (!abr_ops->read_abr_metadata(abr_ops->context, sizeof(serialized), serialized)) {
ABR_ERROR("Failed to read metadata.\n");
return kAbrResultErrorIo;
}
result = abr_data_deserialize(serialized, sizeof(serialized), abr_data);
if (result == kAbrResultErrorUnsupportedVersion) {
/* We don't want to clobber valid data in persistent storage, but we can't use this data, so
* bail out.
*/
return result;
} else if (result != kAbrResultOk) {
/* No valid data exists. Use default data and set original data to trigger update. */
abr_data_init(abr_data);
AbrMemset(abr_data_orig, 0, sizeof(*abr_data_orig));
return kAbrResultOk;
}
} else {
abr_data_init(abr_data);
if (!abr_ops->read_abr_metadata_custom(abr_ops->context, &abr_data->slot_data[kAbrSlotIndexA],
&abr_data->slot_data[kAbrSlotIndexB],
&abr_data->one_shot_recovery_boot)) {
ABR_ERROR("Failed to read metadata.\n");
return kAbrResultErrorIo;
}
}
*abr_data_orig = *abr_data;
slot_normalize(&abr_data->slot_data[kAbrSlotIndexA]);
slot_normalize(&abr_data->slot_data[kAbrSlotIndexB]);
return kAbrResultOk;
}
/* Writes metadata to disk only if it has changed. |abr_data_orig| should be from load_metadata().
*/
static AbrResult save_metadata_if_changed(const AbrOps* abr_ops, AbrData* abr_data,
AbrData* abr_data_orig) {
if (AbrSafeMemcmp(abr_data, abr_data_orig, sizeof(*abr_data)) == 0) {
return kAbrResultOk;
}
return save_metadata(abr_ops, abr_data);
}
static bool check_slot_index(AbrSlotIndex slot_index) {
if (slot_index < kAbrSlotIndexA || slot_index > kAbrSlotIndexR) {
ABR_ERROR("Invalid slot index.\n");
return false;
}
return true;
}
AbrResult AbrGetSlotLastMarkedActive(const AbrOps* abr_ops, AbrSlotIndex* out_slot) {
AbrData abr_data, abr_data_orig;
AbrResult result = load_metadata(abr_ops, &abr_data, &abr_data_orig);
if (result != kAbrResultOk) {
return result;
}
uint8_t priority_a = abr_data.slot_data[kAbrSlotIndexA].priority;
uint8_t priority_b = abr_data.slot_data[kAbrSlotIndexB].priority;
*out_slot = priority_b > priority_a ? kAbrSlotIndexB : kAbrSlotIndexA;
return result;
}
AbrSlotIndex AbrGetBootSlot(const AbrOps* abr_ops, bool update_metadata,
bool* is_slot_marked_successful) {
AbrData abr_data, abr_data_orig;
AbrResult result = kAbrResultOk;
AbrSlotIndex slot_to_boot = kAbrSlotIndexR;
if (is_slot_marked_successful)
*is_slot_marked_successful = false;
result = load_metadata(abr_ops, &abr_data, &abr_data_orig);
if (result != kAbrResultOk) {
ABR_ERROR("Failed to load metadata, falling back to recovery mode.\n");
return kAbrSlotIndexR;
}
/* One-shot recovery boot has the highest priority if metadata can be updated. */
if (abr_data.one_shot_recovery_boot && update_metadata) {
abr_data.one_shot_recovery_boot = 0;
if (save_metadata(abr_ops, &abr_data) == kAbrResultOk) {
return kAbrSlotIndexR;
}
ABR_ERROR("Failed to update one-shot state. Ignoring one-shot request.\n");
/* Put it back how it was, maybe a later boot stage will be able to handle it. */
abr_data.one_shot_recovery_boot = 1;
}
/* Choose the highest priority and bootable slot, if there is one, otherwise R slot*/
slot_to_boot = get_active_slot(&abr_data);
if (slot_to_boot == kAbrSlotIndexR) {
ABR_DEBUG("All slots are unbootable, falling back to recovery mode.\n");
} else {
if (is_slot_marked_successful && abr_data.slot_data[slot_to_boot].successful_boot) {
*is_slot_marked_successful = true;
}
}
if (update_metadata) {
/* In addition to any changes that resulted from normalization, there are a couple changes to be
* made here. First is to decrement the tries remaining for a slot not yet marked as successful.
*/
if ((slot_to_boot != kAbrSlotIndexR) && !abr_data.slot_data[slot_to_boot].successful_boot) {
abr_data.slot_data[slot_to_boot].tries_remaining--;
}
/* Second is to clear the successful_boot bit from any successfully-marked slots that aren't the
* slot we're booting. It's possible that booting from one slot will render the other slot
* unbootable (say, by migrating a config file format in a shared partiton). Clearing these bits
* minimizes the risk we'll have an unhealthy slot marked "successful_boot", which would prevent
* the system from automatically booting into recovery.
*/
if ((slot_to_boot != kAbrSlotIndexA) && abr_data.slot_data[kAbrSlotIndexA].successful_boot) {
abr_data.slot_data[kAbrSlotIndexA].tries_remaining = kAbrMaxTriesRemaining;
abr_data.slot_data[kAbrSlotIndexA].successful_boot = 0;
}
if ((slot_to_boot != kAbrSlotIndexB) && abr_data.slot_data[kAbrSlotIndexB].successful_boot) {
abr_data.slot_data[kAbrSlotIndexB].tries_remaining = kAbrMaxTriesRemaining;
abr_data.slot_data[kAbrSlotIndexB].successful_boot = 0;
}
result = save_metadata_if_changed(abr_ops, &abr_data, &abr_data_orig);
if (result != kAbrResultOk) {
/* We have no choice but to proceed without updating metadata. */
ABR_ERROR("Failed to update metadata, proceeding anyways.\n");
}
}
return slot_to_boot;
}
const char* AbrGetSlotSuffix(AbrSlotIndex slot_index) {
static const char* slot_suffixes[] = {"_a", "_b", "_r"};
if (!check_slot_index(slot_index)) {
return "";
}
return slot_suffixes[slot_index];
}
AbrResult AbrMarkSlotActive(const AbrOps* abr_ops, AbrSlotIndex slot_index) {
AbrData abr_data, abr_data_orig;
AbrSlotIndex other_slot_index;
AbrResult result;
if (!check_slot_index(slot_index)) {
return kAbrResultErrorInvalidData;
}
if (slot_index == kAbrSlotIndexR) {
ABR_ERROR("Invalid argument: Cannot mark slot R as active.\n");
return kAbrResultErrorInvalidData;
}
result = load_metadata(abr_ops, &abr_data, &abr_data_orig);
if (result != kAbrResultOk) {
return result;
}
/* Make requested slot top priority, unsuccessful, and with max tries. */
abr_data.slot_data[slot_index].priority = kAbrMaxPriority;
abr_data.slot_data[slot_index].tries_remaining = kAbrMaxTriesRemaining;
abr_data.slot_data[slot_index].successful_boot = 0;
/* Ensure other slot doesn't have as high a priority. */
other_slot_index = 1 - slot_index;
if (abr_data.slot_data[other_slot_index].priority == kAbrMaxPriority) {
abr_data.slot_data[other_slot_index].priority--;
}
return save_metadata_if_changed(abr_ops, &abr_data, &abr_data_orig);
}
AbrResult AbrMarkSlotUnbootable(const AbrOps* abr_ops, AbrSlotIndex slot_index) {
AbrData abr_data, abr_data_orig;
AbrResult result;
if (!check_slot_index(slot_index)) {
return kAbrResultErrorInvalidData;
}
if (slot_index == kAbrSlotIndexR) {
ABR_ERROR("Invalid argument: Cannot mark slot R as unbootable.\n");
return kAbrResultErrorInvalidData;
}
result = load_metadata(abr_ops, &abr_data, &abr_data_orig);
if (result != kAbrResultOk) {
return result;
}
set_slot_unbootable(&abr_data.slot_data[slot_index]);
return save_metadata_if_changed(abr_ops, &abr_data, &abr_data_orig);
}
AbrResult AbrMarkSlotSuccessful(const AbrOps* abr_ops, AbrSlotIndex slot_index) {
AbrData abr_data, abr_data_orig;
AbrSlotIndex other_slot_index;
AbrResult result;
if (!check_slot_index(slot_index)) {
return kAbrResultErrorInvalidData;
}
if (slot_index == kAbrSlotIndexR) {
ABR_ERROR("Invalid argument: Cannot mark slot R as successful.\n");
return kAbrResultErrorInvalidData;
}
result = load_metadata(abr_ops, &abr_data, &abr_data_orig);
if (result != kAbrResultOk) {
return result;
}
if (!is_slot_bootable(&abr_data.slot_data[slot_index])) {
ABR_ERROR("Invalid argument: Cannot mark unbootable slot as successful.\n");
return kAbrResultErrorInvalidData;
}
abr_data.slot_data[slot_index].tries_remaining = 0;
abr_data.slot_data[slot_index].successful_boot = 1;
/* Remove any success mark on the other slot.
*
* TODO(fxbug.dev/64255): Remove this logic once the fix for fxbug.dev/64057 has rolled out.
*/
other_slot_index = 1 - slot_index;
if (is_slot_bootable(&abr_data.slot_data[other_slot_index])) {
abr_data.slot_data[other_slot_index].tries_remaining = kAbrMaxTriesRemaining;
abr_data.slot_data[other_slot_index].successful_boot = 0;
}
return save_metadata_if_changed(abr_ops, &abr_data, &abr_data_orig);
}
AbrResult AbrGetSlotInfo(const AbrOps* abr_ops, AbrSlotIndex slot_index, AbrSlotInfo* info) {
AbrData abr_data, abr_data_orig;
AbrResult result;
if (info == NULL) {
ABR_ERROR("Invalid argument: |info| cannot be NULL.\n");
return kAbrResultErrorInvalidData;
}
AbrMemset(info, 0, sizeof(*info));
if (!check_slot_index(slot_index)) {
return kAbrResultErrorInvalidData;
}
result = load_metadata(abr_ops, &abr_data, &abr_data_orig);
if (result != kAbrResultOk) {
return result;
}
if (slot_index == kAbrSlotIndexR) {
/* Assume that R slot is always OK. */
info->is_bootable = true;
info->is_active = is_slot_active(&abr_data, kAbrSlotIndexR);
info->is_marked_successful = true;
info->num_tries_remaining = 0;
return kAbrResultOk;
}
info->is_bootable = is_slot_bootable(&abr_data.slot_data[slot_index]);
info->is_active = is_slot_active(&abr_data, slot_index);
info->is_marked_successful = abr_data.slot_data[slot_index].successful_boot;
info->num_tries_remaining = abr_data.slot_data[slot_index].tries_remaining;
return kAbrResultOk;
}
AbrResult AbrSetOneShotRecovery(const AbrOps* abr_ops, bool enable) {
AbrData abr_data, abr_data_orig;
AbrResult result;
result = load_metadata(abr_ops, &abr_data, &abr_data_orig);
if (result != kAbrResultOk) {
return result;
}
abr_data.one_shot_recovery_boot = enable ? 1 : 0;
return save_metadata_if_changed(abr_ops, &abr_data, &abr_data_orig);
}
/* Additional API needed for Estelle. */
bool AbrIsValidMetadata(const uint8_t* buffer, size_t size) {
AbrData data;
return abr_data_deserialize(buffer, size, &data) == kAbrResultOk;
}