From 9304a899df9eb1dfcf5f69a82d047499a78e5f8f Mon Sep 17 00:00:00 2001 From: Ruslan Trofymenko Date: Thu, 6 Dec 2018 19:44:13 +0200 Subject: bootcontrol: Add initial support This patch adds the basic Boot Control HAL implementation that allows to operate A/B metadata on 'misc' partition. The HAL is based on Android reference implementation [1] and provides basic functionality for all interface methods. This module should be enabled by adding lines to the .mk-file, like: PRODUCT_PACKAGES += bootctrl.am57x Also, A/B system updates has to be enabled according to [2] (including on bootloader side). Test: run 'bootctl' on target device [1] bootable/recovery/boot_control [2] https://source.android.com/devices/tech/ota/ab/ab_implement Change-Id: I19852920834eeb3e98e7d6acf06a554e2beaa736 Signed-off-by: Ruslan Trofymenko --- bootctrl/Android.bp | 40 +++++ bootctrl/boot_control.cc | 351 ++++++++++++++++++++++++++++++++++++++++ bootctrl/bootloader_message.cpp | 249 ++++++++++++++++++++++++++++ bootctrl/bootloader_message.h | 249 ++++++++++++++++++++++++++++ 4 files changed, 889 insertions(+) create mode 100644 bootctrl/Android.bp create mode 100644 bootctrl/boot_control.cc create mode 100644 bootctrl/bootloader_message.cpp create mode 100644 bootctrl/bootloader_message.h diff --git a/bootctrl/Android.bp b/bootctrl/Android.bp new file mode 100644 index 0000000..ce5041f --- /dev/null +++ b/bootctrl/Android.bp @@ -0,0 +1,40 @@ +// Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/ +// +// 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. + +cc_library_shared { + name: "bootctrl.am57x", + + vendor: true, + relative_install_path: "hw", + + srcs: [ + "boot_control.cc", + "bootloader_message.cpp" + ], + + cflags: [ + "-DLOG_TAG=\"ti_bootcontrol\"", + ], + + header_libs: ["libhardware_headers"], + + static_libs: ["libfstab",], + + shared_libs: [ + "liblog", + "libcutils", + "libbase", + "libz" + ], +} diff --git a/bootctrl/boot_control.cc b/bootctrl/boot_control.cc new file mode 100644 index 0000000..28c30d4 --- /dev/null +++ b/bootctrl/boot_control.cc @@ -0,0 +1,351 @@ +/* + * Copyright (C) Texas Instruments Incorporated - http://www.ti.com/ + * + * 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 +#include +#include +#include +#include + +#include + +#define BOOT_SLOT_PROP "ro.boot.slot_suffix" + +struct BootControlPrivate { + // The base struct needs to be first in the list. + boot_control_module_t base; + + // Whether this struct was initialized with data from the bootloader message + // that doesn't change until next reboot. + bool initialized; + + // The path to the misc_device as reported in the fstab. + const char* misc_device; + + // The number of slots present on the device. + unsigned int num_slots; + + // The slot where we are running from. + unsigned int current_slot; +}; + +constexpr unsigned int kMaxNumSlots = + sizeof(bootloader_control::slot_info) / + sizeof(bootloader_control::slot_info[0]); + +constexpr const char* kSlotSuffixes[kMaxNumSlots] = { "_a", "_b", "_c", "_d" }; + +// Return the little-endian representation of the CRC-32 of the first fields +// in |boot_ctrl| up to the crc32_le field. +static uint32_t GetBootloaderControlCRC(const bootloader_control* boot_ctrl) { + return crc32(0, (const uint8_t*)boot_ctrl, + offsetof(bootloader_control, crc32_le)); +} + +static bool LoadBootloaderControl(const char* misc_device, + bootloader_control* boot_ctrl) { + std::string str_err; + if (read_bootloader_control_from(boot_ctrl, misc_device, &str_err)) + return true; + + ALOGE("%s", str_err.c_str()); + + return false; +} + +static bool SaveBootloaderControl(const char* misc_device, + bootloader_control* boot_ctrl) { + boot_ctrl->crc32_le = GetBootloaderControlCRC(boot_ctrl); + + std::string str_err; + if (write_bootloader_control_to(boot_ctrl, misc_device, &str_err)) + return true; + + ALOGE("%s", str_err.c_str()); + + return false; +} + +// Return the index of the slot suffix passed or -1 if not a valid slot suffix. +static int SlotSuffixToIndex(const char* suffix) { + for (unsigned int slot = 0; slot < kMaxNumSlots; ++slot) { + if (!strcmp(kSlotSuffixes[slot], suffix)) return slot; + } + + return -1; +} + +static bool IsInitialized(const BootControlPrivate* module) { + if (!module->initialized) { + ALOGW("Module not initialized"); + return false; + } + + return true; +} + +void BootControlInit(boot_control_module_t* module) { + struct BootControlPrivate* bootctrl_module = + reinterpret_cast(module); + + if (bootctrl_module->initialized) return; + + if (!module) { + ALOGE("Invalid argument passed to %s", __func__); + return; + } + + ALOGI("Init %s", module->common.name); + + // Initialize the current_slot from the read-only property. If the property + // was not set (from either the command line or the device tree), we can later + // initialize it from the bootloader_control struct. + char suffix_prop[PROPERTY_VALUE_MAX] = {0}; + property_get(BOOT_SLOT_PROP, suffix_prop, ""); + bootctrl_module->current_slot = SlotSuffixToIndex(suffix_prop); + + std::string err; + std::string device = get_bootloader_message_blk_device(&err); + + bootloader_control boot_ctrl; + if (!LoadBootloaderControl(device.c_str(), &boot_ctrl)) + ALOGE("Error loading metadata"); + + // Note that since there isn't a module unload function this memory is leaked. + bootctrl_module->misc_device = strdup(device.c_str()); + uint32_t computed_crc32 = GetBootloaderControlCRC(&boot_ctrl); + if (boot_ctrl.crc32_le != computed_crc32) { + ALOGE("Invalid boot control found, expected CRC-32 0x%04X, " + "but found 0x%04X. Should re-initializing A/B metadata.", + computed_crc32, boot_ctrl.crc32_le); + return; + } + + std::string metadata_suffix = "_" + std::string(boot_ctrl.slot_suffix); + if (SlotSuffixToIndex(metadata_suffix.c_str()) != + bootctrl_module->current_slot) { + ALOGE("Kernel slot argument and A/B metadata do not match, " + "%s=%s, slot metadata=%s", BOOT_SLOT_PROP, suffix_prop, + boot_ctrl.slot_suffix); + return; + } + + bootctrl_module->initialized = true; + bootctrl_module->num_slots = boot_ctrl.nb_slot; + + ALOGI("Current slot: %s(%d), number of slots: %d", boot_ctrl.slot_suffix, + bootctrl_module->current_slot, bootctrl_module->num_slots); + + return; +} + +unsigned int GetNumberSlots(boot_control_module_t* module) { + BootControlPrivate* const bootctrl_module = + reinterpret_cast(module); + + if (!IsInitialized(bootctrl_module)) return -1; + + return bootctrl_module->num_slots; +} + +unsigned int GetCurrentSlot(boot_control_module_t* module) { + BootControlPrivate* const bootctrl_module = + reinterpret_cast(module); + + if (!IsInitialized(bootctrl_module)) return -1; + + return bootctrl_module->current_slot; +} + +int IsSlotMarkedSuccessful(boot_control_module_t* module, unsigned int slot) { + BootControlPrivate* const bootctrl_module = + reinterpret_cast(module); + + if (!IsInitialized(bootctrl_module)) return -1; + + if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) { + // Invalid slot number. + return -1; + } + + bootloader_control bootctrl; + if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) + return -1; + + return (bootctrl.slot_info[slot].successful_boot && + bootctrl.slot_info[slot].tries_remaining); +} + +int MarkBootSuccessful(boot_control_module_t* module) { + BootControlPrivate* const bootctrl_module = + reinterpret_cast(module); + + if (!IsInitialized(bootctrl_module)) return -1; + + bootloader_control bootctrl; + if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) + return -1; + + bootctrl.slot_info[bootctrl_module->current_slot].successful_boot = 1; + // tries_remaining == 0 means that the slot is not bootable anymore, make + // sure we mark the current slot as bootable if it succeeds in the last + // attempt. + bootctrl.slot_info[bootctrl_module->current_slot].tries_remaining = 1; + if (!SaveBootloaderControl(bootctrl_module->misc_device, &bootctrl)) + return -1; + + ALOGI("Slot %d is marked as successfully booted", + bootctrl_module->current_slot); + + return 0; +} + +int SetActiveBootSlot(boot_control_module_t* module, unsigned int slot) { + BootControlPrivate* const bootctrl_module = + reinterpret_cast(module); + + if (!IsInitialized(bootctrl_module)) + return -1; + + if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) { + // Invalid slot number. + return -1; + } + + bootloader_control bootctrl; + if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) + return -1; + + // Set every other slot with a lower priority than the new "active" slot. + const unsigned int kActivePriority = 15; + const unsigned int kActiveTries = 6; + for (unsigned int i = 0; i < bootctrl_module->num_slots; ++i) { + if (i != slot) { + if (bootctrl.slot_info[i].priority >= kActivePriority) + bootctrl.slot_info[i].priority = kActivePriority - 1; + } + } + + // Note that setting a slot as active doesn't change the successful bit. + // The successful bit will only be changed by setSlotAsUnbootable(). + bootctrl.slot_info[slot].priority = kActivePriority; + bootctrl.slot_info[slot].tries_remaining = kActiveTries; + + // Setting the current slot as active is a way to revert the operation that + // set *another* slot as active at the end of an updater. This is commonly + // used to cancel the pending update. We should only reset the verity_corrpted + // bit when attempting a new slot, otherwise the verity bit on the current + // slot would be flip. + if (slot != bootctrl_module->current_slot) + bootctrl.slot_info[slot].verity_corrupted = 0; + + if (!SaveBootloaderControl(bootctrl_module->misc_device, &bootctrl)) + return -1; + + ALOGI("Slot %d is set as active", slot); + + return 0; +} + +int SetSlotAsUnbootable(boot_control_module_t* module, unsigned int slot) { + BootControlPrivate* const bootctrl_module = + reinterpret_cast(module); + + if (!IsInitialized(bootctrl_module)) + return -1; + + if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) { + // Invalid slot number. + return -1; + } + + bootloader_control bootctrl; + if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) + return -1; + + // The only way to mark a slot as unbootable, regardless of the priority is to + // set the tries_remaining to 0. + bootctrl.slot_info[slot].successful_boot = 0; + bootctrl.slot_info[slot].tries_remaining = 0; + if (!SaveBootloaderControl(bootctrl_module->misc_device, &bootctrl)) + return -1; + + ALOGI("Slot %d is marked as unbootable", slot); + + return 0; +} + +int IsSlotBootable(struct boot_control_module* module, unsigned int slot) { + BootControlPrivate* const bootctrl_module = + reinterpret_cast(module); + + if (!IsInitialized(bootctrl_module)) return -1; + + if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) { + // Invalid slot number. + return -1; + } + + bootloader_control bootctrl; + if (!LoadBootloaderControl(bootctrl_module->misc_device, &bootctrl)) + return -1; + + return bootctrl.slot_info[slot].tries_remaining; +} + +const char* GetSuffix(boot_control_module_t* module, unsigned int slot) { + BootControlPrivate* const bootctrl_module = + reinterpret_cast(module); + + if (!IsInitialized(bootctrl_module)) return NULL; + + if (slot >= kMaxNumSlots || slot >= bootctrl_module->num_slots) return NULL; + + return kSlotSuffixes[slot]; +} + +static hw_module_methods_t boot_control_module_methods = { + .open = NULL, +}; + +BootControlPrivate HAL_MODULE_INFO_SYM = { + .base = { + .common ={ + .tag = HARDWARE_MODULE_TAG, + .module_api_version = 1, + .hal_api_version = 0, + .id = BOOT_CONTROL_HARDWARE_MODULE_ID, + .name = "AM57xx Boot control HAL", + .author = "Texas Instruments", + .methods = &boot_control_module_methods + }, + + .init = BootControlInit, + .getNumberSlots = GetNumberSlots, + .getCurrentSlot = GetCurrentSlot, + .markBootSuccessful = MarkBootSuccessful, + .setActiveBootSlot = SetActiveBootSlot, + .setSlotAsUnbootable = SetSlotAsUnbootable, + .isSlotBootable = IsSlotBootable, + .getSuffix = GetSuffix, + .isSlotMarkedSuccessful = IsSlotMarkedSuccessful + }, + + .initialized = false, + .misc_device = nullptr, + .num_slots = 0, + .current_slot = 0 +}; diff --git a/bootctrl/bootloader_message.cpp b/bootctrl/bootloader_message.cpp new file mode 100644 index 0000000..a4634ed --- /dev/null +++ b/bootctrl/bootloader_message.cpp @@ -0,0 +1,249 @@ +/* + * 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 "bootloader_message.h" + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +constexpr off_t kBootloaderControlOffset = offsetof(bootloader_message_ab, slot_suffix); + +static std::string get_misc_blk_device(std::string* err) { + std::unique_ptr fstab(fs_mgr_read_fstab_default(), + fs_mgr_free_fstab); + if (!fstab) { + *err = "failed to read default fstab"; + return ""; + } + fstab_rec* record = fs_mgr_get_entry_for_mount_point(fstab.get(), "/misc"); + if (record == nullptr) { + *err = "failed to find /misc partition"; + return ""; + } + return record->blk_device; +} + +// In recovery mode, recovery can get started and try to access the misc +// device before the kernel has actually created it. +static bool wait_for_device(const std::string& blk_device, std::string* err) { + int tries = 0; + int ret; + err->clear(); + do { + ++tries; + struct stat buf; + ret = stat(blk_device.c_str(), &buf); + if (ret == -1) { + *err += android::base::StringPrintf("failed to stat %s try %d: %s\n", + blk_device.c_str(), tries, strerror(errno)); + sleep(1); + } + } while (ret && tries < 10); + + if (ret) { + *err += android::base::StringPrintf("failed to stat %s\n", blk_device.c_str()); + } + return ret == 0; +} + +static bool read_misc_partition(void* p, size_t size, const std::string& misc_blk_device, + size_t offset, std::string* err) { + if (!wait_for_device(misc_blk_device, err)) { + return false; + } + android::base::unique_fd fd(open(misc_blk_device.c_str(), O_RDONLY)); + if (fd == -1) { + *err = android::base::StringPrintf("failed to open %s: %s", misc_blk_device.c_str(), + strerror(errno)); + return false; + } + if (lseek(fd, static_cast(offset), SEEK_SET) != static_cast(offset)) { + *err = android::base::StringPrintf("failed to lseek %s: %s", misc_blk_device.c_str(), + strerror(errno)); + return false; + } + if (!android::base::ReadFully(fd, p, size)) { + *err = android::base::StringPrintf("failed to read %s: %s", misc_blk_device.c_str(), + strerror(errno)); + return false; + } + return true; +} + +static bool write_misc_partition(const void* p, size_t size, const std::string& misc_blk_device, + size_t offset, std::string* err) { + android::base::unique_fd fd(open(misc_blk_device.c_str(), O_WRONLY)); + if (fd == -1) { + *err = android::base::StringPrintf("failed to open %s: %s", misc_blk_device.c_str(), + strerror(errno)); + return false; + } + if (lseek(fd, static_cast(offset), SEEK_SET) != static_cast(offset)) { + *err = android::base::StringPrintf("failed to lseek %s: %s", misc_blk_device.c_str(), + strerror(errno)); + return false; + } + if (!android::base::WriteFully(fd, p, size)) { + *err = android::base::StringPrintf("failed to write %s: %s", misc_blk_device.c_str(), + strerror(errno)); + return false; + } + if (fsync(fd) == -1) { + *err = android::base::StringPrintf("failed to fsync %s: %s", misc_blk_device.c_str(), + strerror(errno)); + return false; + } + return true; +} + +std::string get_bootloader_message_blk_device(std::string* err) { + std::string misc_blk_device = get_misc_blk_device(err); + if (misc_blk_device.empty()) return ""; + if (!wait_for_device(misc_blk_device, err)) return ""; + return misc_blk_device; +} + +bool read_bootloader_message_from(bootloader_message* boot, const std::string& misc_blk_device, + std::string* err) { + return read_misc_partition(boot, sizeof(*boot), misc_blk_device, + BOOTLOADER_MESSAGE_OFFSET_IN_MISC, err); +} + +bool read_bootloader_message(bootloader_message* boot, std::string* err) { + std::string misc_blk_device = get_misc_blk_device(err); + if (misc_blk_device.empty()) { + return false; + } + return read_bootloader_message_from(boot, misc_blk_device, err); +} + +bool read_bootloader_control_from(bootloader_control* boot_ctrl, const std::string& misc_blk_device, + std::string* err) { + return read_misc_partition(boot_ctrl, sizeof(bootloader_control), misc_blk_device, + kBootloaderControlOffset, err); +} + +bool write_bootloader_message_to(const bootloader_message& boot, const std::string& misc_blk_device, + std::string* err) { + return write_misc_partition(&boot, sizeof(boot), misc_blk_device, + BOOTLOADER_MESSAGE_OFFSET_IN_MISC, err); +} + +bool write_bootloader_message(const bootloader_message& boot, std::string* err) { + std::string misc_blk_device = get_misc_blk_device(err); + if (misc_blk_device.empty()) { + return false; + } + return write_bootloader_message_to(boot, misc_blk_device, err); +} + +bool write_bootloader_control_to(const bootloader_control* boot_ctrl, const std::string& misc_blk_device, + std::string* err) { + return write_misc_partition(boot_ctrl, sizeof(bootloader_control), misc_blk_device, + kBootloaderControlOffset, err); +} + +bool clear_bootloader_message(std::string* err) { + bootloader_message boot = {}; + return write_bootloader_message(boot, err); +} + +bool write_bootloader_message(const std::vector& options, std::string* err) { + bootloader_message boot = {}; + update_bootloader_message_in_struct(&boot, options); + + return write_bootloader_message(boot, err); +} + +bool update_bootloader_message(const std::vector& options, std::string* err) { + bootloader_message boot; + if (!read_bootloader_message(&boot, err)) { + return false; + } + update_bootloader_message_in_struct(&boot, options); + + return write_bootloader_message(boot, err); +} + +bool update_bootloader_message_in_struct(bootloader_message* boot, + const std::vector& options) { + if (!boot) return false; + // Replace the command & recovery fields. + memset(boot->command, 0, sizeof(boot->command)); + memset(boot->recovery, 0, sizeof(boot->recovery)); + + strlcpy(boot->command, "boot-recovery", sizeof(boot->command)); + strlcpy(boot->recovery, "recovery\n", sizeof(boot->recovery)); + for (const auto& s : options) { + strlcat(boot->recovery, s.c_str(), sizeof(boot->recovery)); + if (s.back() != '\n') { + strlcat(boot->recovery, "\n", sizeof(boot->recovery)); + } + } + return true; +} + +bool write_reboot_bootloader(std::string* err) { + bootloader_message boot; + if (!read_bootloader_message(&boot, err)) { + return false; + } + if (boot.command[0] != '\0') { + *err = "Bootloader command pending."; + return false; + } + strlcpy(boot.command, "bootonce-bootloader", sizeof(boot.command)); + return write_bootloader_message(boot, err); +} + +bool read_wipe_package(std::string* package_data, size_t size, std::string* err) { + std::string misc_blk_device = get_misc_blk_device(err); + if (misc_blk_device.empty()) { + return false; + } + package_data->resize(size); + return read_misc_partition(&(*package_data)[0], size, misc_blk_device, + WIPE_PACKAGE_OFFSET_IN_MISC, err); +} + +bool write_wipe_package(const std::string& package_data, std::string* err) { + std::string misc_blk_device = get_misc_blk_device(err); + if (misc_blk_device.empty()) { + return false; + } + return write_misc_partition(package_data.data(), package_data.size(), misc_blk_device, + WIPE_PACKAGE_OFFSET_IN_MISC, err); +} + +extern "C" bool write_reboot_bootloader(void) { + std::string err; + return write_reboot_bootloader(&err); +} + +extern "C" bool write_bootloader_message(const char* options) { + std::string err; + return write_bootloader_message({options}, &err); +} diff --git a/bootctrl/bootloader_message.h b/bootctrl/bootloader_message.h new file mode 100644 index 0000000..e5b9489 --- /dev/null +++ b/bootctrl/bootloader_message.h @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2008 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. + */ + +#ifndef _BOOTLOADER_MESSAGE_H +#define _BOOTLOADER_MESSAGE_H + +#include +#include +#include + +// Spaces used by misc partition are as below: +// 0 - 2K For bootloader_message +// 2K - 16K Used by Vendor's bootloader (the 2K - 4K range may be optionally used +// as bootloader_message_ab struct) +// 16K - 64K Used by uncrypt and recovery to store wipe_package for A/B devices +// Note that these offsets are admitted by bootloader,recovery and uncrypt, so they +// are not configurable without changing all of them. +static const size_t BOOTLOADER_MESSAGE_OFFSET_IN_MISC = 0; +static const size_t WIPE_PACKAGE_OFFSET_IN_MISC = 16 * 1024; + +/* Bootloader Message (2-KiB) + * + * This structure describes the content of a block in flash + * that is used for recovery and the bootloader to talk to + * each other. + * + * The command field is updated by linux when it wants to + * reboot into recovery or to update radio or bootloader firmware. + * It is also updated by the bootloader when firmware update + * is complete (to boot into recovery for any final cleanup) + * + * The status field was used by the bootloader after the completion + * of an "update-radio" or "update-hboot" command, which has been + * deprecated since Froyo. + * + * The recovery field is only written by linux and used + * for the system to send a message to recovery or the + * other way around. + * + * The stage field is written by packages which restart themselves + * multiple times, so that the UI can reflect which invocation of the + * package it is. If the value is of the format "#/#" (eg, "1/3"), + * the UI will add a simple indicator of that status. + * + * We used to have slot_suffix field for A/B boot control metadata in + * this struct, which gets unintentionally cleared by recovery or + * uncrypt. Move it into struct bootloader_message_ab to avoid the + * issue. + */ +struct bootloader_message { + char command[32]; + char status[32]; + char recovery[768]; + + // The 'recovery' field used to be 1024 bytes. It has only ever + // been used to store the recovery command line, so 768 bytes + // should be plenty. We carve off the last 256 bytes to store the + // stage string (for multistage packages) and possible future + // expansion. + char stage[32]; + + // The 'reserved' field used to be 224 bytes when it was initially + // carved off from the 1024-byte recovery field. Bump it up to + // 1184-byte so that the entire bootloader_message struct rounds up + // to 2048-byte. + char reserved[1184]; +}; + +/** + * We must be cautious when changing the bootloader_message struct size, + * because A/B-specific fields may end up with different offsets. + */ +#if (__STDC_VERSION__ >= 201112L) || defined(__cplusplus) +static_assert(sizeof(struct bootloader_message) == 2048, + "struct bootloader_message size changes, which may break A/B devices"); +#endif + +/** + * The A/B-specific bootloader message structure (4-KiB). + * + * We separate A/B boot control metadata from the regular bootloader + * message struct and keep it here. Everything that's A/B-specific + * stays after struct bootloader_message, which should be managed by + * the A/B-bootloader or boot control HAL. + * + * The slot_suffix field is used for A/B implementations where the + * bootloader does not set the androidboot.ro.boot.slot_suffix kernel + * commandline parameter. This is used by fs_mgr to mount /system and + * other partitions with the slotselect flag set in fstab. A/B + * implementations are free to use all 32 bytes and may store private + * data past the first NUL-byte in this field. It is encouraged, but + * not mandatory, to use 'struct bootloader_control' described below. + * + * The update_channel field is used to store the Omaha update channel + * if update_engine is compiled with Omaha support. + */ +struct bootloader_message_ab { + struct bootloader_message message; + char slot_suffix[32]; + char update_channel[128]; + + // Round up the entire struct to 4096-byte. + char reserved[1888]; +}; + +/** + * Be cautious about the struct size change, in case we put anything post + * bootloader_message_ab struct (b/29159185). + */ +#if (__STDC_VERSION__ >= 201112L) || defined(__cplusplus) +static_assert(sizeof(struct bootloader_message_ab) == 4096, + "struct bootloader_message_ab size changes"); +#endif + +#define BOOT_CTRL_MAGIC 0x42414342 /* Bootloader Control AB */ +#define BOOT_CTRL_VERSION 1 + +struct slot_metadata { + // Slot priority with 15 meaning highest priority, 1 lowest + // priority and 0 the slot is unbootable. + uint8_t priority : 4; + // Number of times left attempting to boot this slot. + uint8_t tries_remaining : 3; + // 1 if this slot has booted successfully, 0 otherwise. + uint8_t successful_boot : 1; + // 1 if this slot is corrupted from a dm-verity corruption, 0 + // otherwise. + uint8_t verity_corrupted : 1; + // Reserved for further use. + uint8_t reserved : 7; +} __attribute__((packed)); + +/* Bootloader Control AB + * + * This struct can be used to manage A/B metadata. It is designed to + * be put in the 'slot_suffix' field of the 'bootloader_message' + * structure described above. It is encouraged to use the + * 'bootloader_control' structure to store the A/B metadata, but not + * mandatory. + */ +struct bootloader_control { + // NUL terminated active slot suffix. + char slot_suffix[4]; + // Bootloader Control AB magic number (see BOOT_CTRL_MAGIC). + uint32_t magic; + // Version of struct being used (see BOOT_CTRL_VERSION). + uint8_t version; + // Number of slots being managed. + uint8_t nb_slot : 3; + // Number of times left attempting to boot recovery. + uint8_t recovery_tries_remaining : 3; + // Ensure 4-bytes alignment for slot_info field. + uint8_t reserved0[2]; + // Per-slot information. Up to 4 slots. + struct slot_metadata slot_info[4]; + // Reserved for further use. + uint8_t reserved1[8]; + // CRC32 of all 28 bytes preceding this field (little endian + // format). + uint32_t crc32_le; +} __attribute__((packed)); + +#if (__STDC_VERSION__ >= 201112L) || defined(__cplusplus) +static_assert(sizeof(struct bootloader_control) == + sizeof(((struct bootloader_message_ab *)0)->slot_suffix), + "struct bootloader_control has wrong size"); +#endif + +#ifdef __cplusplus + +#include +#include + +// Return the block device name for the bootloader message partition and waits +// for the device for up to 10 seconds. In case of error returns the empty +// string. +std::string get_bootloader_message_blk_device(std::string* err); + +// Read bootloader message into boot. Error message will be set in err. +bool read_bootloader_message(bootloader_message* boot, std::string* err); + +// Read bootloader message from the specified misc device into boot. +bool read_bootloader_message_from(bootloader_message* boot, const std::string& misc_blk_device, + std::string* err); + +// Read bootloader control block from the specified misc device into boot_ctrl. +bool read_bootloader_control_from(bootloader_control* boot_ctrl, const std::string& misc_blk_device, + std::string* err); + +// Write bootloader message to BCB. +bool write_bootloader_message(const bootloader_message& boot, std::string* err); + +// Write bootloader message to the specified BCB device. +bool write_bootloader_message_to(const bootloader_message& boot, + const std::string& misc_blk_device, std::string* err); + +// Write bootloader message (boots into recovery with the options) to BCB. Will +// set the command and recovery fields, and reset the rest. +bool write_bootloader_message(const std::vector& options, std::string* err); + +// Write bootloader control block to the specified BCB device. +bool write_bootloader_control_to(const bootloader_control* boot_ctrl, const std::string& misc_blk_device, + std::string* err); + +// Update bootloader message (boots into recovery with the options) to BCB. Will +// only update the command and recovery fields. +bool update_bootloader_message(const std::vector& options, std::string* err); + +// Update bootloader message (boots into recovery with the |options|) in |boot|. Will only update +// the command and recovery fields. +bool update_bootloader_message_in_struct(bootloader_message* boot, + const std::vector& options); + +// Clear BCB. +bool clear_bootloader_message(std::string* err); + +// Writes the reboot-bootloader reboot reason to the bootloader_message. +bool write_reboot_bootloader(std::string* err); + +// Read the wipe package from BCB (from offset WIPE_PACKAGE_OFFSET_IN_MISC). +bool read_wipe_package(std::string* package_data, size_t size, std::string* err); + +// Write the wipe package into BCB (to offset WIPE_PACKAGE_OFFSET_IN_MISC). +bool write_wipe_package(const std::string& package_data, std::string* err); + +#else + +#include + +// C Interface. +bool write_bootloader_message(const char* options); +bool write_reboot_bootloader(void); + +#endif // ifdef __cplusplus + +#endif // _BOOTLOADER_MESSAGE_H -- cgit v1.2.3