| /* |
| * Copyright (C) 2023 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. |
| */ |
| |
| #define LOG_TAG "bootcontrolhal" |
| |
| #include "BootControl.h" |
| |
| #include <android-base/file.h> |
| #include <android-base/logging.h> |
| #include <android-base/properties.h> |
| #include <android-base/unique_fd.h> |
| #include <bootloader_message/bootloader_message.h> |
| #include <cutils/properties.h> |
| #include <libboot_control/libboot_control.h> |
| #include <log/log.h> |
| #include <trusty/tipc.h> |
| |
| #include "DevInfo.h" |
| #include "GptUtils.h" |
| |
| using HIDLMergeStatus = ::android::bootable::BootControl::MergeStatus; |
| using ndk::ScopedAStatus; |
| |
| using android::bootable::GetMiscVirtualAbMergeStatus; |
| using android::bootable::InitMiscVirtualAbMessageIfNeeded; |
| using android::bootable::SetMiscVirtualAbMergeStatus; |
| |
| namespace aidl::android::hardware::boot { |
| |
| namespace { |
| |
| // clang-format off |
| |
| #define BOOT_A_PATH "/dev/block/by-name/boot_a" |
| #define BOOT_B_PATH "/dev/block/by-name/boot_b" |
| #define DEVINFO_PATH "/dev/block/by-name/devinfo" |
| |
| #define BLOW_AR_PATH "/sys/kernel/boot_control/blow_ar" |
| |
| // slot flags |
| #define AB_ATTR_PRIORITY_SHIFT 52 |
| #define AB_ATTR_PRIORITY_MASK (3UL << AB_ATTR_PRIORITY_SHIFT) |
| #define AB_ATTR_ACTIVE_SHIFT 54 |
| #define AB_ATTR_ACTIVE (1UL << AB_ATTR_ACTIVE_SHIFT) |
| #define AB_ATTR_RETRY_COUNT_SHIFT (55) |
| #define AB_ATTR_RETRY_COUNT_MASK (7UL << AB_ATTR_RETRY_COUNT_SHIFT) |
| #define AB_ATTR_SUCCESSFUL (1UL << 58) |
| #define AB_ATTR_UNBOOTABLE (1UL << 59) |
| |
| #define AB_ATTR_MAX_PRIORITY 3UL |
| #define AB_ATTR_MAX_RETRY_COUNT 3UL |
| |
| // clang-format on |
| |
| static std::string getDevPath(int32_t in_slot) { |
| char real_path[PATH_MAX]; |
| |
| const char *path = in_slot == 0 ? BOOT_A_PATH : BOOT_B_PATH; |
| |
| int ret = readlink(path, real_path, sizeof real_path); |
| if (ret < 0) { |
| ALOGE("readlink failed for boot device %s\n", strerror(errno)); |
| return std::string(); |
| } |
| |
| std::string dp(real_path); |
| // extract /dev/sda.. part |
| return dp.substr(0, sizeof "/dev/block/sdX" - 1); |
| } |
| |
| static bool isSlotFlagSet(int32_t in_slot, uint64_t flag) { |
| std::string dev_path = getDevPath(in_slot); |
| if (dev_path.empty()) { |
| ALOGI("Could not get device path for slot %d\n", in_slot); |
| return false; |
| } |
| |
| GptUtils gpt(dev_path); |
| if (gpt.Load()) { |
| ALOGI("failed to load gpt data\n"); |
| return false; |
| } |
| |
| gpt_entry *e = gpt.GetPartitionEntry(in_slot ? "boot_b" : "boot_a"); |
| if (e == nullptr) { |
| ALOGI("failed to get gpt entry\n"); |
| return false; |
| } |
| |
| return !!(e->attr & flag); |
| } |
| |
| static bool setSlotFlag(int32_t in_slot, uint64_t flag) { |
| std::string dev_path = getDevPath(in_slot); |
| if (dev_path.empty()) { |
| ALOGI("Could not get device path for slot %d\n", in_slot); |
| return false; |
| } |
| |
| GptUtils gpt(dev_path); |
| if (gpt.Load()) { |
| ALOGI("failed to load gpt data\n"); |
| return false; |
| } |
| |
| gpt_entry *e = gpt.GetPartitionEntry(in_slot ? "boot_b" : "boot_a"); |
| if (e == nullptr) { |
| ALOGI("failed to get gpt entry\n"); |
| return false; |
| } |
| |
| e->attr |= flag; |
| gpt.Sync(); |
| |
| return true; |
| } |
| |
| static bool is_devinfo_valid; |
| static bool is_devinfo_initialized; |
| static std::mutex devinfo_lock; |
| static devinfo_t devinfo; |
| |
| static bool isDevInfoValid() { |
| const std::lock_guard<std::mutex> lock(devinfo_lock); |
| |
| if (is_devinfo_initialized) { |
| return is_devinfo_valid; |
| } |
| |
| is_devinfo_initialized = true; |
| |
| ::android::base::unique_fd fd(open(DEVINFO_PATH, O_RDONLY)); |
| ::android::base::ReadFully(fd, &devinfo, sizeof devinfo); |
| |
| if (devinfo.magic != DEVINFO_MAGIC) { |
| return is_devinfo_valid; |
| } |
| |
| uint32_t version = ((uint32_t)devinfo.ver_major << 16) | devinfo.ver_minor; |
| // only version 3.3+ supports A/B data |
| if (version >= 0x0003'0003) { |
| is_devinfo_valid = true; |
| } |
| |
| return is_devinfo_valid; |
| } |
| |
| static bool DevInfoSync() { |
| if (!isDevInfoValid()) { |
| return false; |
| } |
| |
| ::android::base::unique_fd fd(open(DEVINFO_PATH, O_WRONLY | O_DSYNC)); |
| return ::android::base::WriteFully(fd, &devinfo, sizeof devinfo); |
| } |
| |
| static void DevInfoInitSlot(devinfo_ab_slot_data_t &slot_data) { |
| slot_data.retry_count = AB_ATTR_MAX_RETRY_COUNT; |
| slot_data.unbootable = 0; |
| slot_data.successful = 0; |
| slot_data.active = 1; |
| slot_data.fastboot_ok = 0; |
| } |
| |
| static int blow_otp_AR(bool secure) { |
| static const char *dev_name = "/dev/trusty-ipc-dev0"; |
| static const char *otp_name = "com.android.trusty.otp_manager.tidl"; |
| int fd = 1, ret = 0; |
| uint32_t cmd = secure? OTP_CMD_write_antirbk_secure_ap : OTP_CMD_write_antirbk_non_secure_ap; |
| fd = tipc_connect(dev_name, otp_name); |
| if (fd < 0) { |
| ALOGI("Failed to connect to OTP_MGR ns TA - is it missing?\n"); |
| ret = -1; |
| return ret; |
| } |
| |
| struct otp_mgr_req_base req = { |
| .command = cmd, |
| .resp_payload_size = 0, |
| }; |
| struct iovec iov[] = { |
| { |
| .iov_base = &req, |
| .iov_len = sizeof(req), |
| }, |
| }; |
| |
| size_t rc = tipc_send(fd, iov, 1, NULL, 0); |
| if (rc != sizeof(req)) { |
| ALOGI("Send fail! %zx\n", rc); |
| return rc; |
| } |
| |
| struct otp_mgr_rsp_base resp; |
| rc = read(fd, &resp, sizeof(resp)); |
| if (rc < 0) { |
| ALOGI("Read fail! %zx\n", rc); |
| return rc; |
| } |
| |
| if (rc < sizeof(resp)) { |
| ALOGI("Not enough data! %zx\n", rc); |
| return -EIO; |
| } |
| |
| if (resp.command != (cmd | OTP_RESP_BIT)) { |
| ALOGI("Wrong command! %x\n", resp.command); |
| return -EINVAL; |
| } |
| |
| if (resp.result != 0) { |
| fprintf(stderr, "AR writing error! %x\n", resp.result); |
| return -EINVAL; |
| } |
| |
| tipc_close(fd); |
| return 0; |
| } |
| |
| static bool blowAR_zuma() { |
| int ret = blow_otp_AR(true); |
| if (ret) { |
| ALOGI("Blow secure anti-rollback OTP failed"); |
| return false; |
| } |
| |
| ret = blow_otp_AR(false); |
| if (ret) { |
| ALOGI("Blow non-secure anti-rollback OTP failed"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool blowAR_gs101() { |
| ::android::base::unique_fd fd(open(BLOW_AR_PATH, O_WRONLY | O_DSYNC)); |
| return ::android::base::WriteStringToFd("1", fd); |
| } |
| |
| static bool blowAR() { |
| const auto& platform = ::android::base::GetProperty("ro.boot.hardware.platform", ""); |
| |
| if (platform == "gs101") { |
| return blowAR_gs101(); |
| } else if (platform == "gs201" || platform == "zuma" || platform == "zumapro") { |
| return blowAR_zuma(); |
| } |
| |
| return false; |
| } |
| |
| static constexpr MergeStatus ToAIDLMergeStatus(HIDLMergeStatus status) { |
| switch (status) { |
| case HIDLMergeStatus::NONE: |
| return MergeStatus::NONE; |
| case HIDLMergeStatus::UNKNOWN: |
| return MergeStatus::UNKNOWN; |
| case HIDLMergeStatus::SNAPSHOTTED: |
| return MergeStatus::SNAPSHOTTED; |
| case HIDLMergeStatus::MERGING: |
| return MergeStatus::MERGING; |
| case HIDLMergeStatus::CANCELLED: |
| return MergeStatus::CANCELLED; |
| } |
| } |
| |
| static constexpr HIDLMergeStatus ToHIDLMergeStatus(MergeStatus status) { |
| switch (status) { |
| case MergeStatus::NONE: |
| return HIDLMergeStatus::NONE; |
| case MergeStatus::UNKNOWN: |
| return HIDLMergeStatus::UNKNOWN; |
| case MergeStatus::SNAPSHOTTED: |
| return HIDLMergeStatus::SNAPSHOTTED; |
| case MergeStatus::MERGING: |
| return HIDLMergeStatus::MERGING; |
| case MergeStatus::CANCELLED: |
| return HIDLMergeStatus::CANCELLED; |
| } |
| } |
| |
| } // namespace |
| |
| BootControl::BootControl() { |
| CHECK(InitMiscVirtualAbMessageIfNeeded()); |
| } |
| |
| ScopedAStatus BootControl::getActiveBootSlot(int32_t* _aidl_return) { |
| int32_t slots = 0; |
| getNumberSlots(&slots); |
| if (slots == 0) { |
| *_aidl_return = 0; |
| return ScopedAStatus::ok(); |
| } |
| |
| if (isDevInfoValid()) { |
| *_aidl_return = devinfo.ab_data.slots[1].active ? 1 : 0; |
| return ScopedAStatus::ok(); |
| } |
| *_aidl_return = isSlotFlagSet(1, AB_ATTR_ACTIVE) ? 1 : 0; |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus BootControl::getCurrentSlot(int32_t* _aidl_return) { |
| char suffix[PROPERTY_VALUE_MAX]; |
| property_get("ro.boot.slot_suffix", suffix, "_a"); |
| *_aidl_return = std::string(suffix) == "_b" ? 1 : 0; |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus BootControl::getNumberSlots(int32_t* _aidl_return) { |
| int32_t slots = 0; |
| |
| if (access(BOOT_A_PATH, F_OK) == 0) |
| slots++; |
| |
| if (access(BOOT_B_PATH, F_OK) == 0) |
| slots++; |
| |
| *_aidl_return = slots; |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus BootControl::getSnapshotMergeStatus(MergeStatus* _aidl_return) { |
| HIDLMergeStatus status; |
| int32_t current_slot = 0; |
| getCurrentSlot(¤t_slot); |
| if (!GetMiscVirtualAbMergeStatus(current_slot, &status)) { |
| *_aidl_return = MergeStatus::UNKNOWN; |
| return ScopedAStatus::ok(); |
| } |
| *_aidl_return = ToAIDLMergeStatus(status); |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus BootControl::getSuffix(int32_t in_slot, std::string* _aidl_return) { |
| *_aidl_return = in_slot == 0 ? "_a" : in_slot == 1 ? "_b" : ""; |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus BootControl::isSlotBootable(int32_t in_slot, bool* _aidl_return) { |
| int32_t slots = 0; |
| getNumberSlots(&slots); |
| if (slots == 0) { |
| *_aidl_return = false; |
| return ScopedAStatus::ok(); |
| } |
| if (in_slot >= slots) |
| return ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| INVALID_SLOT, (std::string("Invalid slot ") + std::to_string(in_slot)).c_str()); |
| |
| bool unbootable; |
| if (isDevInfoValid()) { |
| auto &slot_data = devinfo.ab_data.slots[in_slot]; |
| unbootable = !!slot_data.unbootable; |
| } else { |
| unbootable = isSlotFlagSet(in_slot, AB_ATTR_UNBOOTABLE); |
| } |
| |
| *_aidl_return = unbootable ? false: true; |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus BootControl::isSlotMarkedSuccessful(int32_t in_slot, bool* _aidl_return) { |
| int32_t slots = 0; |
| getNumberSlots(&slots); |
| if (slots == 0) { |
| // just return true so that we don't we another call trying to mark it as successful |
| // when there is no slots |
| *_aidl_return = true; |
| return ScopedAStatus::ok(); |
| } |
| if (in_slot < 0 || in_slot >= slots) |
| return ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| INVALID_SLOT, (std::string("Invalid slot ") + std::to_string(in_slot)).c_str()); |
| |
| bool successful; |
| if (isDevInfoValid()) { |
| auto &slot_data = devinfo.ab_data.slots[in_slot]; |
| successful = !!slot_data.successful; |
| } else { |
| successful = isSlotFlagSet(in_slot, AB_ATTR_SUCCESSFUL); |
| } |
| |
| *_aidl_return = successful ? true : false; |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus BootControl::markBootSuccessful() { |
| int32_t slots = 0; |
| getNumberSlots(&slots); |
| if (slots == 0) { |
| // no slots, just return true otherwise Android keeps trying |
| return ScopedAStatus::ok(); |
| } |
| |
| bool ret; |
| int32_t current_slot = 0; |
| getCurrentSlot(¤t_slot); |
| if (isDevInfoValid()) { |
| auto const slot = current_slot; |
| devinfo.ab_data.slots[slot].successful = 1; |
| ret = DevInfoSync(); |
| } else { |
| ret = setSlotFlag(current_slot, AB_ATTR_SUCCESSFUL); |
| } |
| |
| if (!ret) { |
| return ScopedAStatus::fromServiceSpecificErrorWithMessage(COMMAND_FAILED, |
| "Failed to set successful flag"); |
| } |
| |
| if (!blowAR()) { |
| ALOGE("Failed to blow anti-rollback counter"); |
| // Ignore the error, since ABL will re-trigger it on reboot |
| } |
| |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus BootControl::setActiveBootSlot(int32_t in_slot) { |
| if (in_slot >= 2) { |
| return ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| INVALID_SLOT, (std::string("Invalid slot ") + std::to_string(in_slot)).c_str()); |
| } |
| |
| if (isDevInfoValid()) { |
| auto &active_slot_data = devinfo.ab_data.slots[in_slot]; |
| auto &inactive_slot_data = devinfo.ab_data.slots[!in_slot]; |
| |
| inactive_slot_data.active = 0; |
| DevInfoInitSlot(active_slot_data); |
| |
| if (!DevInfoSync()) { |
| return ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| COMMAND_FAILED, "Could not update DevInfo data"); |
| } |
| } else { |
| std::string dev_path = getDevPath(in_slot); |
| if (dev_path.empty()) { |
| return ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| COMMAND_FAILED, "Could not get device path for slot"); |
| } |
| |
| GptUtils gpt(dev_path); |
| if (gpt.Load()) { |
| return ScopedAStatus::fromServiceSpecificErrorWithMessage(COMMAND_FAILED, |
| "failed to load gpt data"); |
| } |
| |
| gpt_entry *active_entry = gpt.GetPartitionEntry(in_slot == 0 ? "boot_a" : "boot_b"); |
| gpt_entry *inactive_entry = gpt.GetPartitionEntry(in_slot == 0 ? "boot_b" : "boot_a"); |
| if (active_entry == nullptr || inactive_entry == nullptr) { |
| return ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| COMMAND_FAILED, "failed to get entries for boot partitions"); |
| } |
| |
| ALOGV("slot active attributes %lx\n", active_entry->attr); |
| ALOGV("slot inactive attributes %lx\n", inactive_entry->attr); |
| |
| // update attributes for active and inactive |
| inactive_entry->attr &= ~AB_ATTR_ACTIVE; |
| active_entry->attr = AB_ATTR_ACTIVE | (AB_ATTR_MAX_PRIORITY << AB_ATTR_PRIORITY_SHIFT) | |
| (AB_ATTR_MAX_RETRY_COUNT << AB_ATTR_RETRY_COUNT_SHIFT); |
| } |
| |
| char boot_dev[PROPERTY_VALUE_MAX]; |
| property_get("ro.boot.bootdevice", boot_dev, ""); |
| if (boot_dev[0] == '\0') { |
| ALOGI("failed to get ro.boot.bootdevice. try ro.boot.boot_devices\n"); |
| property_get("ro.boot.boot_devices", boot_dev, ""); |
| if (boot_dev[0] == '\0') { |
| return ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| COMMAND_FAILED, "invalid ro.boot.bootdevice and ro.boot.boot_devices prop"); |
| } |
| } |
| |
| std::string boot_lun_path = |
| std::string("/sys/devices/platform/") + boot_dev + "/pixel/boot_lun_enabled"; |
| int fd = open(boot_lun_path.c_str(), O_RDWR | O_DSYNC); |
| if (fd < 0) { |
| // Try old path for kernels < 5.4 |
| // TODO: remove once kernel 4.19 support is deprecated |
| std::string boot_lun_path = |
| std::string("/sys/devices/platform/") + boot_dev + "/attributes/boot_lun_enabled"; |
| fd = open(boot_lun_path.c_str(), O_RDWR | O_DSYNC); |
| if (fd < 0) { |
| return ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| COMMAND_FAILED, "failed to open ufs attr boot_lun_enabled"); |
| } |
| } |
| |
| // |
| // bBootLunEn |
| // 0x1 => Boot LU A = enabled, Boot LU B = disable |
| // 0x2 => Boot LU A = disable, Boot LU B = enabled |
| // |
| int ret = ::android::base::WriteStringToFd(in_slot == 0 ? "1" : "2", fd); |
| close(fd); |
| if (ret < 0) { |
| return ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| COMMAND_FAILED, "faied to write boot_lun_enabled attribute"); |
| } |
| |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus BootControl::setSlotAsUnbootable(int32_t in_slot) { |
| if (in_slot >= 2) |
| return ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| INVALID_SLOT, (std::string("Invalid slot ") + std::to_string(in_slot)).c_str()); |
| |
| if (isDevInfoValid()) { |
| auto &slot_data = devinfo.ab_data.slots[in_slot]; |
| slot_data.unbootable = 1; |
| if (!DevInfoSync()) { |
| return ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| COMMAND_FAILED, "Could not update DevInfo data"); |
| } |
| } else { |
| std::string dev_path = getDevPath(in_slot); |
| if (dev_path.empty()) { |
| return ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| COMMAND_FAILED, "Could not get device path for slot"); |
| } |
| |
| GptUtils gpt(dev_path); |
| gpt.Load(); |
| |
| gpt_entry *e = gpt.GetPartitionEntry(in_slot ? "boot_b" : "boot_a"); |
| e->attr |= AB_ATTR_UNBOOTABLE; |
| |
| gpt.Sync(); |
| } |
| |
| return ScopedAStatus::ok(); |
| } |
| |
| ScopedAStatus BootControl::setSnapshotMergeStatus(MergeStatus in_status) { |
| int32_t current_slot = 0; |
| getCurrentSlot(¤t_slot); |
| if (!SetMiscVirtualAbMergeStatus(current_slot, ToHIDLMergeStatus(in_status))) |
| return ScopedAStatus::fromServiceSpecificErrorWithMessage(COMMAND_FAILED, |
| "Operation failed"); |
| return ScopedAStatus::ok(); |
| } |
| |
| } // namespace aidl::android::hardware::boot |