blob: 7a6df928cde8ed4c9ee545c9180b00977ba67b8b [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "bub_ab_flow.h"
#include "bub_util.h"
static const uint8_t magic[] = BUB_BOOT_CTRL_MAGIC;
static const char* bub_suffixes[2] = {"_a", "_b"};
static int normalize_slot(BubSlotData *slot) {
if (slot->priority > 0) {
if (slot->tries_remaining == 0 && !slot->successful_boot) {
bub_memset(slot, 0, sizeof(BubSlotData));
return 1;
}
if (slot->tries_remaining > 0 && slot->successful_boot) {
slot->successful_boot = 0;
return 1;
}
} else if (slot->tries_remaining != 0 || slot->successful_boot) {
bub_memset(slot, 0, sizeof(BubSlotData));
return 1;
}
return 0;
}
static int slot_is_bootable(const BubSlotData *slot) {
return (slot->successful_boot || slot->tries_remaining > 0);
}
static int reset_metadata(BubOps* ops) {
BubAbData metadata;
bub_memset(&metadata, 0, sizeof(BubAbData));
bub_memcpy(&metadata.magic, magic, sizeof(metadata.magic));
metadata.major_version = BUB_MAJOR_VERSION;
metadata.minor_version = BUB_MINOR_VERSION;
metadata.slots[0].priority = 15;
metadata.slots[0].tries_remaining = 7;
metadata.slots[0].successful_boot = 0;
metadata.slots[1].priority = 15;
metadata.slots[1].tries_remaining = 7;
metadata.slots[1].successful_boot = 0;
return bub_write_ab_data_to_misc(ops, &metadata);
}
BubAbFlowResult bub_ab_flow(BubOps* ops,
char* out_selected_suffix,
size_t suffix_num_bytes) {
bub_assert(out_selected_suffix != NULL);
bub_assert(suffix_num_bytes >= 3);
BubAbFlowResult ab_err;
BubAbData ab_ctl;
int target_slot_index_to_boot = -1;
int new_metadata = 0;
// No selection has been made yet.
bub_memset(out_selected_suffix, 0, 3);
ab_err = bub_read_ab_data_from_misc(ops, &ab_ctl);
if (ab_err != BUB_AB_FLOW_RESULT_OK) {
if (ab_err == BUB_AB_FLOW_ERROR_INVALID_METADATA) {
bub_warning("Reseting metadata.\n");
if (!reset_metadata(ops)) {
bub_warning("Unable to reset metadata.\n");
}
}
return ab_err;
}
// Ensure only proper slot states exist.
if (normalize_slot(&ab_ctl.slots[0])) {
bub_warning("State of slot A was normalized.\n");
new_metadata = 1;
}
if (normalize_slot(&ab_ctl.slots[1])) {
bub_warning("State of slot B was normalized.\n");
new_metadata = 1;
}
if (slot_is_bootable(&ab_ctl.slots[0]) && slot_is_bootable(&ab_ctl.slots[1]))
{
if (ab_ctl.slots[1].priority > ab_ctl.slots[0].priority)
target_slot_index_to_boot = 1;
else
target_slot_index_to_boot = 0;
} else if (slot_is_bootable(&ab_ctl.slots[0])) {
// Choose slot A.
target_slot_index_to_boot = 0;
} else if (slot_is_bootable(&ab_ctl.slots[1])) {
// Choose slot B.
target_slot_index_to_boot = 1;
} else {
if (new_metadata && !bub_write_ab_data_to_misc(ops, &ab_ctl))
return BUB_AB_FLOW_ERROR_WRITE_METADATA;
// If neither was chosen, there are no valid slots.
bub_warning("No valid slot found.\n");
return BUB_AB_FLOW_ERROR_NO_VALID_SLOTS;
}
// Found usable slot to attempt boot on. Decrement tries remaining and write
// out the new AB metadata if the slots is in "Updated" state.
if (ab_ctl.slots[target_slot_index_to_boot].tries_remaining > 0) {
ab_ctl.slots[target_slot_index_to_boot].tries_remaining--;
new_metadata = 1;
}
if (new_metadata)
if (!bub_write_ab_data_to_misc(ops, &ab_ctl))
return BUB_AB_FLOW_ERROR_WRITE_METADATA;
// Write selected suffix to caller's pointer.
bub_memcpy(out_selected_suffix, bub_suffixes[target_slot_index_to_boot], 3);
return BUB_AB_FLOW_RESULT_OK;
}
int bub_ab_mark_as_invalid(BubOps* ops, const char* invalid_suffix) {
bub_assert(invalid_suffix != NULL);
bub_assert(bub_strlen(invalid_suffix) >= 2);
BubAbData ab_ctl;
BubAbFlowResult ab_err;
unsigned int i;
ab_err = bub_read_ab_data_from_misc(ops, &ab_ctl);
if (ab_err != BUB_AB_FLOW_RESULT_OK)
return ab_err;
// Find suffix in small list.
for (i = 0; i < 2; ++i)
{
if (!bub_memcmp(bub_suffixes[i], invalid_suffix, 3)) {
// Found the corresponding index to invalid_suffix. Invalidate the slot
// and write out.
bub_memset(&ab_ctl.slots[i], 0, sizeof(BubSlotData));
return bub_write_ab_data_to_misc(ops, &ab_ctl);
}
}
bub_warning("Could not find requested slot.\n");
return 0;
}
BubAbFlowResult bub_read_ab_data_from_misc(BubOps* ops, BubAbData* data) {
BubIOResult io_result;
size_t num_bytes_read;
uint32_t crc;
io_result = ops->read_from_partition(ops, "misc", data, 0,
sizeof(BubAbData), &num_bytes_read);
if (io_result != BUB_IO_RESULT_OK || num_bytes_read != sizeof(BubAbData)) {
bub_warning("Could not read metadata from misc partition.\n");
return BUB_AB_FLOW_ERROR_READ_METADATA;
}
if(bub_memcmp(data->magic, magic, 4) != 0) {
bub_warning("AB metadata magic did not match.\n");
return BUB_AB_FLOW_ERROR_INVALID_METADATA;
}
crc = bub_be32toh(data->crc32);
data->crc32 = 0;
if (crc != bub_crc32(0, data, sizeof(BubAbData))) {
bub_warning("AB metadata crc is invalid.\n");
return BUB_AB_FLOW_ERROR_INVALID_METADATA;
}
// Assign host byte order to necessary variables needed here.
data->crc32 = crc;
return BUB_AB_FLOW_RESULT_OK;
}
int bub_write_ab_data_to_misc(BubOps* ops, const BubAbData* data) {
BubIOResult io_result;
BubAbData metadata;
bub_memcpy(&metadata, data, sizeof(BubAbData));
// Assign big endian order to necessary variables here.
// Calculate crc assign back to crc field, maintaining big endianness.
metadata.crc32 = 0;
metadata.crc32 = bub_be32toh(bub_crc32(0, &metadata, sizeof(BubAbData)));
io_result = ops->write_to_partition(ops,
"misc",
&metadata,
0,
sizeof(BubAbData));
if (io_result != BUB_IO_RESULT_OK) {
bub_warning("Could not write to misc partition.\n");
return 0;
}
return 1;
}