ANDROID: Initial support for the Android Bootloader flow
An Android Bootloader must comply with certain boot modes and change
the kernel command line accordingly. This patch introduces the Android
boot mode concept which determines whether the device should boot to
one of the following:
* recovery: which should boot to the recovery image,
* bootloader: which should boot to the "bootloader" (fastboot) and
* normal: which should boot to the system image.
The boot mode is determined in part by the Boot Control Block (BCB)
which is stored at the beginning of the "misc" partition. The BCB
is defined in the "bootloader_message.h" file in AOSP, now copied
here as android_bootloader_message.h with minor modifications.
This patch implements the basic boot flow that loads and boots an
Android kernel image assuming an A/B device which implies that it uses
boot as recovery (BOARD_USES_RECOVERY_AS_BOOT in the BoardConfig.mk).
This means that the recovery image shares the same kernel with the
normal boot system image, but stores the recovery image as a ramdisk
which is not used in normal mode.
Among the limitations, this patch doesn't implement the A/B slot
selection, it only boots from the slot "a".
Bug: 31887729
Bug: 141272741
Test: Booted a rpi3 with this flow.
Change-Id: I4b1ff93145b1d73dc250e1fc2c9ec005f8fe147e
Signed-off-by: Alex Deymo <deymo@google.com>
[astrachan: Merged in bits of CMD_LOAD_ANDROID, without the actual
load_android command, to keep this change building]
Signed-off-by: Alistair Strachan <astrachan@google.com>
Signed-off-by: David Anderson <dvander@google.com>
Link: https://android-review.googlesource.com/c/platform/external/u-boot/+/1126443/7
Bug: https://baylibre.atlassian.net/browse/RITA-97
Signed-off-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
diff --git a/README b/README
index 1389e8f..bb5e28e 100644
--- a/README
+++ b/README
@@ -1153,6 +1153,17 @@
entering dfuMANIFEST state. Host waits this timeout, before
sending again an USB request to the device.
+- Android Bootloader support:
+ CONFIG_ANDROID_BOOTLOADER
+ This enables support for the Android bootloader flow. Android
+ devices can boot in normal mode, recovery mode or bootloader
+ mode. The normal mode is the most common boot mode, but
+ recovery mode is often used to perform factory reset and OTA
+ (over-the-air) updates in the legacy updater. Also it is
+ possible for an Android system to request a reboot to the
+ "bootloader", which often means reboot to fastboot but may also
+ include a UI with a menu.
+
- Journaling Flash filesystem support:
CONFIG_JFFS2_NAND
Define these for a default partition on a NAND device
diff --git a/common/Kconfig b/common/Kconfig
index 28d5e9a..ca1c896 100644
--- a/common/Kconfig
+++ b/common/Kconfig
@@ -880,6 +880,19 @@
displayed immediately after the model is shown on the console
early in boot.
+config ANDROID_BOOTLOADER
+ bool "Support for Android Bootloader boot flow"
+ default n
+ depends on ANDROID_BOOT_IMAGE
+ help
+ If enabled, adds support to boot an Android device following the
+ Android Bootloader boot flow. This flow requires an Android Bootloader
+ to handle the Android Bootloader Message stored in the Boot Control
+ Block (BCB), normally in the "misc" partition of an Android device.
+ The BCB is used to determine the boot mode of the device (normal mode,
+ recovery mode or bootloader mode) and, if enabled, the slot to boot
+ from in devices with multiple boot slots (A/B devices).
+
menu "Start-up hooks"
config ARCH_EARLY_INIT_R
diff --git a/common/Makefile b/common/Makefile
index 302d8be..9b3fc05 100644
--- a/common/Makefile
+++ b/common/Makefile
@@ -109,6 +109,7 @@
obj-y += image.o
obj-$(CONFIG_ANDROID_AB) += android_ab.o
obj-$(CONFIG_ANDROID_BOOT_IMAGE) += image-android.o
+obj-$(CONFIG_ANDROID_BOOTLOADER) += android_bootloader.o
obj-$(CONFIG_$(SPL_TPL_)OF_LIBFDT) += image-fdt.o
obj-$(CONFIG_$(SPL_TPL_)FIT) += image-fit.o
obj-$(CONFIG_$(SPL_)MULTI_DTB_FIT) += boot_fit.o common_fit.o
diff --git a/common/android_bootloader.c b/common/android_bootloader.c
new file mode 100644
index 0000000..cdc77a8
--- /dev/null
+++ b/common/android_bootloader.c
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <android_bootloader.h>
+#include <android_bootloader_message.h>
+
+#include <cli.h>
+#include <common.h>
+#include <malloc.h>
+
+#define ANDROID_PARTITION_BOOT "boot"
+#define ANDROID_PARTITION_SYSTEM "system"
+
+#define ANDROID_ARG_SLOT_SUFFIX "androidboot.slot_suffix="
+#define ANDROID_ARG_ROOT "root="
+
+static int android_bootloader_message_load(
+ struct blk_desc *dev_desc,
+ const disk_partition_t *part_info,
+ struct bootloader_message *message)
+{
+ ulong message_blocks = sizeof(struct bootloader_message) /
+ part_info->blksz;
+ if (message_blocks > part_info->size) {
+ printf("misc partition too small.\n");
+ return -1;
+ }
+
+ if (blk_dread(dev_desc, part_info->start, message_blocks, message) !=
+ message_blocks) {
+ printf("Could not read from misc partition\n");
+ return -1;
+ }
+ debug("ANDROID: Loaded BCB, %lu blocks.\n", message_blocks);
+ return 0;
+}
+
+static int android_bootloader_message_write(
+ struct blk_desc *dev_desc,
+ const disk_partition_t *part_info,
+ struct bootloader_message *message)
+{
+ ulong message_blocks = sizeof(struct bootloader_message) /
+ part_info->blksz;
+ if (message_blocks > part_info->size) {
+ printf("misc partition too small.\n");
+ return -1;
+ }
+
+ if (blk_dwrite(dev_desc, part_info->start, message_blocks, message) !=
+ message_blocks) {
+ printf("Could not write to misc partition\n");
+ return -1;
+ }
+ debug("ANDROID: Wrote new BCB, %lu blocks.\n", message_blocks);
+ return 0;
+}
+
+static enum android_boot_mode android_bootloader_load_and_clear_mode(
+ struct blk_desc *dev_desc,
+ const disk_partition_t *misc_part_info)
+{
+ struct bootloader_message bcb;
+
+#ifdef CONFIG_FASTBOOT
+ char *bootloader_str;
+
+ /* Check for message from bootloader stored in RAM from a previous boot.
+ */
+ bootloader_str = (char *)CONFIG_FASTBOOT_BUF_ADDR;
+ if (!strcmp("reboot-bootloader", bootloader_str)) {
+ bootloader_str[0] = '\0';
+ return ANDROID_BOOT_MODE_BOOTLOADER;
+ }
+#endif
+
+ /* Check and update the BCB message if needed. */
+ if (android_bootloader_message_load(dev_desc, misc_part_info, &bcb) <
+ 0) {
+ printf("WARNING: Unable to load the BCB.\n");
+ return ANDROID_BOOT_MODE_NORMAL;
+ }
+
+ if (!strcmp("bootonce-bootloader", bcb.command)) {
+ /* Erase the message in the BCB since this value should be used
+ * only once.
+ */
+ memset(bcb.command, 0, sizeof(bcb.command));
+ android_bootloader_message_write(dev_desc, misc_part_info,
+ &bcb);
+ return ANDROID_BOOT_MODE_BOOTLOADER;
+ }
+
+ if (!strcmp("boot-recovery", bcb.command))
+ return ANDROID_BOOT_MODE_RECOVERY;
+
+ return ANDROID_BOOT_MODE_NORMAL;
+}
+
+/**
+ * Return the reboot reason string for the passed boot mode.
+ *
+ * @param mode The Android Boot mode.
+ * @return a pointer to the reboot reason string for mode.
+ */
+static const char *android_boot_mode_str(enum android_boot_mode mode)
+{
+ switch (mode) {
+ case ANDROID_BOOT_MODE_NORMAL:
+ return "(none)";
+ case ANDROID_BOOT_MODE_RECOVERY:
+ return "recovery";
+ case ANDROID_BOOT_MODE_BOOTLOADER:
+ return "bootloader";
+ }
+ return NULL;
+}
+
+static int android_part_get_info_by_name_suffix(struct blk_desc *dev_desc,
+ const char *base_name,
+ const char *slot_suffix,
+ disk_partition_t *part_info)
+{
+ char *part_name;
+ int part_num;
+
+ part_name = malloc(strlen(base_name) + strlen(slot_suffix) + 1);
+ if (!part_name)
+ return -1;
+ strcpy(part_name, base_name);
+ strcat(part_name, slot_suffix);
+
+ part_num = part_get_info_by_name(dev_desc, part_name, part_info);
+ if (part_num < 0) {
+ debug("ANDROID: Could not find partition \"%s\"\n", part_name);
+ part_num = -1;
+ }
+
+ free(part_name);
+ return part_num;
+}
+
+static int android_bootloader_boot_bootloader(void)
+{
+ const char *fastboot_cmd = env_get("fastbootcmd");
+
+ if (fastboot_cmd)
+ return run_command(fastboot_cmd, CMD_FLAG_ENV);
+ return -1;
+}
+
+static int android_bootloader_boot_kernel(unsigned long kernel_address)
+{
+ char kernel_addr_str[12];
+ char *fdt_addr = env_get("fdt_addr");
+ char *bootm_args[] = { "bootm", kernel_addr_str, "-", fdt_addr, NULL };
+
+ sprintf(kernel_addr_str, "0x%lx", kernel_address);
+
+ printf("Booting kernel at %s with fdt at %s...\n\n\n",
+ kernel_addr_str, fdt_addr);
+ do_bootm(NULL, 0, 4, bootm_args);
+
+ return -1;
+}
+
+static char *strjoin(const char **chunks, char separator)
+{
+ int len, joined_len = 0;
+ char *ret, *current;
+ const char **p;
+
+ for (p = chunks; *p; p++)
+ joined_len += strlen(*p) + 1;
+
+ if (!joined_len) {
+ ret = malloc(1);
+ if (ret)
+ ret[0] = '\0';
+ return ret;
+ }
+
+ ret = malloc(joined_len);
+ current = ret;
+ if (!ret)
+ return ret;
+
+ for (p = chunks; *p; p++) {
+ len = strlen(*p);
+ memcpy(current, *p, len);
+ current += len;
+ *current = separator;
+ current++;
+ }
+ /* Replace the last separator by a \0. */
+ current[-1] = '\0';
+ return ret;
+}
+
+/** android_assemble_cmdline - Assemble the command line to pass to the kernel
+ * @return a newly allocated string
+ */
+static char *android_assemble_cmdline(const char *slot_suffix,
+ const char *extra_args)
+{
+ const char *cmdline_chunks[16];
+ const char **current_chunk = cmdline_chunks;
+ char *env_cmdline, *cmdline, *rootdev_input;
+ char *allocated_suffix = NULL;
+ char *allocated_rootdev = NULL;
+ unsigned long rootdev_len;
+
+ env_cmdline = env_get("bootargs");
+ if (env_cmdline)
+ *(current_chunk++) = env_cmdline;
+
+ /* The |slot_suffix| needs to be passed to the kernel to know what
+ * slot to boot from.
+ */
+ if (slot_suffix) {
+ allocated_suffix = malloc(strlen(ANDROID_ARG_SLOT_SUFFIX) +
+ strlen(slot_suffix));
+ strcpy(allocated_suffix, ANDROID_ARG_SLOT_SUFFIX);
+ strcat(allocated_suffix, slot_suffix);
+ *(current_chunk++) = allocated_suffix;
+ }
+
+ rootdev_input = env_get("android_rootdev");
+ if (rootdev_input) {
+ rootdev_len = strlen(ANDROID_ARG_ROOT) + CONFIG_SYS_CBSIZE + 1;
+ allocated_rootdev = malloc(rootdev_len);
+ strcpy(allocated_rootdev, ANDROID_ARG_ROOT);
+ cli_simple_process_macros(rootdev_input,
+ allocated_rootdev +
+ strlen(ANDROID_ARG_ROOT));
+ /* Make sure that the string is null-terminated since the
+ * previous could not copy to the end of the input string if it
+ * is too big.
+ */
+ allocated_rootdev[rootdev_len - 1] = '\0';
+ *(current_chunk++) = allocated_rootdev;
+ }
+
+ if (extra_args)
+ *(current_chunk++) = extra_args;
+
+ *(current_chunk++) = NULL;
+ cmdline = strjoin(cmdline_chunks, ' ');
+ free(allocated_suffix);
+ free(allocated_rootdev);
+ return cmdline;
+}
+
+int android_bootloader_boot_flow(struct blk_desc *dev_desc,
+ const disk_partition_t *misc_part_info,
+ unsigned long kernel_address)
+{
+ enum android_boot_mode mode;
+ disk_partition_t boot_part_info;
+ disk_partition_t system_part_info;
+ int boot_part_num, system_part_num;
+ int ret;
+ char *command_line;
+ /* TODO: lookup the slot_suffix based on the BCB. */
+ const char *slot_suffix = "_a";
+ const char *mode_cmdline = NULL;
+
+ /* Determine the boot mode and clear its value for the next boot if
+ * needed.
+ */
+ mode = android_bootloader_load_and_clear_mode(dev_desc, misc_part_info);
+ printf("ANDROID: reboot reason: \"%s\"\n", android_boot_mode_str(mode));
+
+ switch (mode) {
+ case ANDROID_BOOT_MODE_NORMAL:
+ /* In normal mode, we load the kernel from "boot" but append
+ * "skip_initramfs" to the cmdline to make it ignore the
+ * recovery initramfs in the boot partition.
+ */
+ mode_cmdline = "skip_initramfs";
+ break;
+ case ANDROID_BOOT_MODE_RECOVERY:
+ /* In recovery mode we still boot the kernel from "boot" but
+ * don't skip the initramfs so it boots to recovery.
+ */
+ break;
+ case ANDROID_BOOT_MODE_BOOTLOADER:
+ /* Bootloader mode enters fastboot. If this operation fails we
+ * simply return since we can't recover from this situation by
+ * switching to another slot.
+ */
+ return android_bootloader_boot_bootloader();
+ }
+
+ /* Load the kernel from the desired "boot" partition. */
+ boot_part_num =
+ android_part_get_info_by_name_suffix(dev_desc,
+ ANDROID_PARTITION_BOOT,
+ slot_suffix, &boot_part_info);
+ if (boot_part_num < 0)
+ return -1;
+ debug("ANDROID: Loading kernel from \"%s\", partition %d.\n",
+ boot_part_info.name, boot_part_num);
+
+ system_part_num =
+ android_part_get_info_by_name_suffix(dev_desc,
+ ANDROID_PARTITION_SYSTEM,
+ slot_suffix,
+ &system_part_info);
+ if (system_part_num < 0)
+ return -1;
+ debug("ANDROID: Using system image from \"%s\", partition %d.\n",
+ system_part_info.name, system_part_num);
+
+ ret = android_image_load(dev_desc, &boot_part_info, kernel_address,
+ -1UL);
+ if (ret < 0)
+ return ret;
+
+ /* Set Android root variables. */
+ env_set_ulong("android_root_devnum", dev_desc->devnum);
+ env_set_ulong("android_root_partnum", system_part_num);
+ env_set("android_slotsufix", slot_suffix);
+
+ /* Assemble the command line */
+ command_line = android_assemble_cmdline(slot_suffix, mode_cmdline);
+ env_set("bootargs", command_line);
+
+ debug("ANDROID: bootargs: \"%s\"\n", command_line);
+
+ android_bootloader_boot_kernel(kernel_address);
+
+ /* TODO: If the kernel doesn't boot mark the selected slot as bad. */
+ return -1;
+}
diff --git a/common/image-android.c b/common/image-android.c
index 264bf90..a00ecb7 100644
--- a/common/image-android.c
+++ b/common/image-android.c
@@ -8,6 +8,7 @@
#include <image.h>
#include <android_image.h>
#include <malloc.h>
+#include <mapmem.h>
#include <errno.h>
#include <asm/unaligned.h>
@@ -169,6 +170,56 @@
return 0;
}
+long android_image_load(struct blk_desc *dev_desc,
+ const disk_partition_t *part_info,
+ unsigned long load_address,
+ unsigned long max_size) {
+ void *buf;
+ long blk_cnt, blk_read = 0;
+
+ if (max_size < part_info->blksz)
+ return -1;
+
+ /* We don't know the size of the Android image before reading the header
+ * so we don't limit the size of the mapped memory.
+ */
+ buf = map_sysmem(load_address, 0 /* size */);
+
+ /* Read the Android header first and then read the rest. */
+ if (blk_dread(dev_desc, part_info->start, 1, buf) != 1)
+ blk_read = -1;
+
+ if (!blk_read && android_image_check_header(buf) != 0) {
+ printf("** Invalid Android Image header **\n");
+ blk_read = -1;
+ }
+ if (!blk_read) {
+ blk_cnt = (android_image_get_end(buf) - (ulong)buf +
+ part_info->blksz - 1) / part_info->blksz;
+ if (blk_cnt * part_info->blksz > max_size) {
+ debug("Android Image too big (%lu bytes, max %lu)\n",
+ android_image_get_end(buf) - (ulong)buf,
+ max_size);
+ blk_read = -1;
+ } else {
+ debug("Loading Android Image (%lu blocks) to 0x%lx... ",
+ blk_cnt, load_address);
+ blk_read = blk_dread(dev_desc, part_info->start,
+ blk_cnt, buf);
+ }
+ }
+
+ unmap_sysmem(buf);
+ if (blk_read < 0)
+ return blk_read;
+
+ debug("%lu blocks read: %s\n",
+ blk_read, (blk_read == blk_cnt) ? "OK" : "ERROR");
+ if (blk_read != blk_cnt)
+ return -1;
+ return blk_read;
+}
+
int android_image_get_second(const struct andr_img_hdr *hdr,
ulong *second_data, ulong *second_len)
{
diff --git a/include/android_bootloader.h b/include/android_bootloader.h
new file mode 100644
index 0000000..947913e
--- /dev/null
+++ b/include/android_bootloader.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#ifndef __ANDROID_BOOTLOADER_H
+#define __ANDROID_BOOTLOADER_H
+
+#include <common.h>
+
+enum android_boot_mode {
+ ANDROID_BOOT_MODE_NORMAL = 0,
+
+ /* "recovery" mode is triggered by the "reboot recovery" command or
+ * equivalent adb/fastboot command. It can also be triggered by writing
+ * "boot-recovery" in the BCB message. This mode should boot the
+ * recovery kernel.
+ */
+ ANDROID_BOOT_MODE_RECOVERY,
+
+ /* "bootloader" mode is triggered by the "reboot bootloader" command or
+ * equivalent adb/fastboot command. It can also be triggered by writing
+ * "bootonce-bootloader" in the BCB message. This mode should boot into
+ * fastboot.
+ */
+ ANDROID_BOOT_MODE_BOOTLOADER,
+};
+
+/** android_bootloader_boot_flow - Execute the Android Bootloader Flow.
+ * Performs the Android Bootloader boot flow, loading the appropriate Android
+ * image (normal kernel, recovery kernel or "bootloader" mode) and booting it.
+ * The boot mode is determined by the contents of the Android Bootloader
+ * Message. On success it doesn't return.
+ *
+ * @return a negative number in case of error, otherwise it doesn't return.
+ */
+int android_bootloader_boot_flow(struct blk_desc *dev_desc,
+ const disk_partition_t *misc_part_info,
+ unsigned long kernel_address);
+
+#endif /* __ANDROID_BOOTLOADER_H */
diff --git a/include/image.h b/include/image.h
index c1065c0..f96cc1e 100644
--- a/include/image.h
+++ b/include/image.h
@@ -1338,6 +1338,25 @@
ulong android_image_get_kcomp(const struct andr_img_hdr *hdr);
void android_print_contents(const struct andr_img_hdr *hdr);
+/** android_image_load - Load an Android Image from storage.
+ *
+ * Load an Android Image based on the header size in the storage. Return the
+ * number of bytes read from storage, which could be bigger than the actual
+ * Android Image as described in the header size. In case of error reading the
+ * image or if the image size needed to be read from disk is bigger than the
+ * the passed |max_size| a negative number is returned.
+ *
+ * @dev_desc: The device where to read the image from
+ * @part_info: The partition in |dev_desc| where to read the image from
+ * @load_address: The address where the image will be loaded
+ * @max_size: The maximum loaded size, in bytes
+ * @return the number of bytes read or a negative number in case of error.
+ */
+long android_image_load(struct blk_desc *dev_desc,
+ const disk_partition_t *part_info,
+ unsigned long load_address,
+ unsigned long max_size);
+
#endif /* CONFIG_ANDROID_BOOT_IMAGE */
/**