mirror of
				https://github.com/smaeul/u-boot.git
				synced 2025-10-31 03:58:17 +00:00 
			
		
		
		
	When booting into Android fastbootd (a subset of recovery), the default UI shows the bootloader version in the screen [1]. This is done via the ro.bootloader property which should come from the bootloader. Provide the U-Boot version via a kernel commandline argument so that fastbootd can show it properly on the screen. [1] https://android.googlesource.com/platform/bootable/recovery/+/refs/heads/main/fastboot/fastboot.cpp#42 Reviewed-by: Julien Masson <jmasson@baylibre.com> Reviewed-by: Guillaume La Roque <glaroque@baylibre.com> Reviewed-by: Simon Glass <sjg@chromium.org> Link: https://lore.kernel.org/r/20240912-bootmeth-bootloader-version-v2-1-76d4c0b45024@baylibre.com Signed-off-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
		
			
				
	
	
		
			565 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			565 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0+
 | |
| /*
 | |
|  * Bootmeth for Android
 | |
|  *
 | |
|  * Copyright (C) 2024 BayLibre, SAS
 | |
|  * Written by Mattijs Korpershoek <mkorpershoek@baylibre.com>
 | |
|  */
 | |
| #define LOG_CATEGORY UCLASS_BOOTSTD
 | |
| 
 | |
| #include <android_ab.h>
 | |
| #include <android_image.h>
 | |
| #if CONFIG_IS_ENABLED(AVB_VERIFY)
 | |
| #include <avb_verify.h>
 | |
| #endif
 | |
| #include <bcb.h>
 | |
| #include <blk.h>
 | |
| #include <bootflow.h>
 | |
| #include <bootm.h>
 | |
| #include <bootmeth.h>
 | |
| #include <dm.h>
 | |
| #include <image.h>
 | |
| #include <malloc.h>
 | |
| #include <mapmem.h>
 | |
| #include <part.h>
 | |
| #include <version.h>
 | |
| #include "bootmeth_android.h"
 | |
| 
 | |
| #define BCB_FIELD_COMMAND_SZ 32
 | |
| #define BCB_PART_NAME "misc"
 | |
| #define BOOT_PART_NAME "boot"
 | |
| #define VENDOR_BOOT_PART_NAME "vendor_boot"
 | |
| 
 | |
| /**
 | |
|  * struct android_priv - Private data
 | |
|  *
 | |
|  * This is read from the disk and recorded for use when the full Android
 | |
|  * kernel must be loaded and booted
 | |
|  *
 | |
|  * @boot_mode: Requested boot mode (normal, recovery, bootloader)
 | |
|  * @slot: Nul-terminated partition slot suffix read from BCB ("a\0" or "b\0")
 | |
|  * @header_version: Android boot image header version
 | |
|  */
 | |
| struct android_priv {
 | |
| 	enum android_boot_mode boot_mode;
 | |
| 	char slot[2];
 | |
| 	u32 header_version;
 | |
| };
 | |
| 
 | |
| static int android_check(struct udevice *dev, struct bootflow_iter *iter)
 | |
| {
 | |
| 	/* This only works on mmc devices */
 | |
| 	if (bootflow_iter_check_mmc(iter))
 | |
| 		return log_msg_ret("mmc", -ENOTSUPP);
 | |
| 
 | |
| 	/*
 | |
| 	 * This only works on whole devices, as multiple
 | |
| 	 * partitions are needed to boot Android
 | |
| 	 */
 | |
| 	if (iter->part != 0)
 | |
| 		return log_msg_ret("mmc part", -ENOTSUPP);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int scan_boot_part(struct udevice *blk, struct android_priv *priv)
 | |
| {
 | |
| 	struct blk_desc *desc = dev_get_uclass_plat(blk);
 | |
| 	struct disk_partition partition;
 | |
| 	char partname[PART_NAME_LEN];
 | |
| 	ulong num_blks, bufsz;
 | |
| 	char *buf;
 | |
| 	int ret;
 | |
| 
 | |
| 	sprintf(partname, BOOT_PART_NAME "_%s", priv->slot);
 | |
| 	ret = part_get_info_by_name(desc, partname, &partition);
 | |
| 	if (ret < 0)
 | |
| 		return log_msg_ret("part info", ret);
 | |
| 
 | |
| 	num_blks = DIV_ROUND_UP(sizeof(struct andr_boot_img_hdr_v0), desc->blksz);
 | |
| 	bufsz = num_blks * desc->blksz;
 | |
| 	buf = malloc(bufsz);
 | |
| 	if (!buf)
 | |
| 		return log_msg_ret("buf", -ENOMEM);
 | |
| 
 | |
| 	ret = blk_read(blk, partition.start, num_blks, buf);
 | |
| 	if (ret != num_blks) {
 | |
| 		free(buf);
 | |
| 		return log_msg_ret("part read", -EIO);
 | |
| 	}
 | |
| 
 | |
| 	if (!is_android_boot_image_header(buf)) {
 | |
| 		free(buf);
 | |
| 		return log_msg_ret("header", -ENOENT);
 | |
| 	}
 | |
| 
 | |
| 	priv->header_version = ((struct andr_boot_img_hdr_v0 *)buf)->header_version;
 | |
| 	free(buf);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int scan_vendor_boot_part(struct udevice *blk, struct android_priv *priv)
 | |
| {
 | |
| 	struct blk_desc *desc = dev_get_uclass_plat(blk);
 | |
| 	struct disk_partition partition;
 | |
| 	char partname[PART_NAME_LEN];
 | |
| 	ulong num_blks, bufsz;
 | |
| 	char *buf;
 | |
| 	int ret;
 | |
| 
 | |
| 	sprintf(partname, VENDOR_BOOT_PART_NAME "_%s", priv->slot);
 | |
| 	ret = part_get_info_by_name(desc, partname, &partition);
 | |
| 	if (ret < 0)
 | |
| 		return log_msg_ret("part info", ret);
 | |
| 
 | |
| 	num_blks = DIV_ROUND_UP(sizeof(struct andr_vnd_boot_img_hdr), desc->blksz);
 | |
| 	bufsz = num_blks * desc->blksz;
 | |
| 	buf = malloc(bufsz);
 | |
| 	if (!buf)
 | |
| 		return log_msg_ret("buf", -ENOMEM);
 | |
| 
 | |
| 	ret = blk_read(blk, partition.start, num_blks, buf);
 | |
| 	if (ret != num_blks) {
 | |
| 		free(buf);
 | |
| 		return log_msg_ret("part read", -EIO);
 | |
| 	}
 | |
| 
 | |
| 	if (!is_android_vendor_boot_image_header(buf)) {
 | |
| 		free(buf);
 | |
| 		return log_msg_ret("header", -ENOENT);
 | |
| 	}
 | |
| 	free(buf);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int android_read_slot_from_bcb(struct bootflow *bflow, bool decrement)
 | |
| {
 | |
| 	struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
 | |
| 	struct android_priv *priv = bflow->bootmeth_priv;
 | |
| 	struct disk_partition misc;
 | |
| 	char slot_suffix[3];
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = part_get_info_by_name(desc, BCB_PART_NAME, &misc);
 | |
| 	if (ret < 0)
 | |
| 		return log_msg_ret("part", ret);
 | |
| 
 | |
| 	ret = ab_select_slot(desc, &misc, decrement);
 | |
| 	if (ret < 0)
 | |
| 		return log_msg_ret("slot", ret);
 | |
| 
 | |
| 	priv->slot[0] = BOOT_SLOT_NAME(ret);
 | |
| 	priv->slot[1] = '\0';
 | |
| 
 | |
| 	sprintf(slot_suffix, "_%s", priv->slot);
 | |
| 	ret = bootflow_cmdline_set_arg(bflow, "androidboot.slot_suffix",
 | |
| 				       slot_suffix, false);
 | |
| 	if (ret < 0)
 | |
| 		return log_msg_ret("cmdl", ret);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int configure_serialno(struct bootflow *bflow)
 | |
| {
 | |
| 	char *serialno = env_get("serial#");
 | |
| 
 | |
| 	if (!serialno)
 | |
| 		return log_msg_ret("serial", -ENOENT);
 | |
| 
 | |
| 	return bootflow_cmdline_set_arg(bflow, "androidboot.serialno", serialno, false);
 | |
| }
 | |
| 
 | |
| static int configure_bootloader_version(struct bootflow *bflow)
 | |
| {
 | |
| 	return bootflow_cmdline_set_arg(bflow, "androidboot.bootloader",
 | |
| 					PLAIN_VERSION, false);
 | |
| }
 | |
| 
 | |
| static int android_read_bootflow(struct udevice *dev, struct bootflow *bflow)
 | |
| {
 | |
| 	struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
 | |
| 	struct disk_partition misc;
 | |
| 	struct android_priv *priv;
 | |
| 	char command[BCB_FIELD_COMMAND_SZ];
 | |
| 	int ret;
 | |
| 
 | |
| 	bflow->state = BOOTFLOWST_MEDIA;
 | |
| 
 | |
| 	/*
 | |
| 	 * bcb_find_partition_and_load() will print errors to stdout
 | |
| 	 * if BCB_PART_NAME is not found. To avoid that, check if the
 | |
| 	 * partition exists first.
 | |
| 	 */
 | |
| 	ret = part_get_info_by_name(desc, BCB_PART_NAME, &misc);
 | |
| 	if (ret < 0)
 | |
| 		return log_msg_ret("part", ret);
 | |
| 
 | |
| 	ret = bcb_find_partition_and_load("mmc", desc->devnum, BCB_PART_NAME);
 | |
| 	if (ret < 0)
 | |
| 		return log_msg_ret("bcb load", ret);
 | |
| 
 | |
| 	ret = bcb_get(BCB_FIELD_COMMAND, command, sizeof(command));
 | |
| 	if (ret < 0)
 | |
| 		return log_msg_ret("bcb read", ret);
 | |
| 
 | |
| 	priv = malloc(sizeof(struct android_priv));
 | |
| 	if (!priv)
 | |
| 		return log_msg_ret("buf", -ENOMEM);
 | |
| 
 | |
| 	if (!strcmp("bootonce-bootloader", command)) {
 | |
| 		priv->boot_mode = ANDROID_BOOT_MODE_BOOTLOADER;
 | |
| 		bflow->os_name = strdup("Android (bootloader)");
 | |
| 	} else if (!strcmp("boot-fastboot", command)) {
 | |
| 		priv->boot_mode = ANDROID_BOOT_MODE_RECOVERY;
 | |
| 		bflow->os_name = strdup("Android (fastbootd)");
 | |
| 	} else if (!strcmp("boot-recovery", command)) {
 | |
| 		priv->boot_mode = ANDROID_BOOT_MODE_RECOVERY;
 | |
| 		bflow->os_name = strdup("Android (recovery)");
 | |
| 	} else {
 | |
| 		priv->boot_mode = ANDROID_BOOT_MODE_NORMAL;
 | |
| 		bflow->os_name = strdup("Android");
 | |
| 	}
 | |
| 	if (!bflow->os_name)
 | |
| 		return log_msg_ret("os", -ENOMEM);
 | |
| 
 | |
| 	if (priv->boot_mode == ANDROID_BOOT_MODE_BOOTLOADER) {
 | |
| 		/* Clear BCB */
 | |
| 		memset(command, 0, sizeof(command));
 | |
| 		ret = bcb_set(BCB_FIELD_COMMAND, command);
 | |
| 		if (ret < 0) {
 | |
| 			free(priv);
 | |
| 			return log_msg_ret("bcb set", ret);
 | |
| 		}
 | |
| 		ret = bcb_store();
 | |
| 		if (ret < 0) {
 | |
| 			free(priv);
 | |
| 			return log_msg_ret("bcb store", ret);
 | |
| 		}
 | |
| 
 | |
| 		bflow->bootmeth_priv = priv;
 | |
| 		bflow->state = BOOTFLOWST_READY;
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	bflow->bootmeth_priv = priv;
 | |
| 
 | |
| 	/* For recovery and normal boot, we need to scan the partitions */
 | |
| 	ret = android_read_slot_from_bcb(bflow, false);
 | |
| 	if (ret < 0) {
 | |
| 		log_err("read slot: %d", ret);
 | |
| 		goto free_priv;
 | |
| 	}
 | |
| 
 | |
| 	ret = scan_boot_part(bflow->blk, priv);
 | |
| 	if (ret < 0) {
 | |
| 		log_debug("scan boot failed: err=%d\n", ret);
 | |
| 		goto free_priv;
 | |
| 	}
 | |
| 
 | |
| 	if (priv->header_version != 4) {
 | |
| 		log_debug("only boot.img v4 is supported %u\n", priv->header_version);
 | |
| 		ret = -EINVAL;
 | |
| 		goto free_priv;
 | |
| 	}
 | |
| 
 | |
| 	ret = scan_vendor_boot_part(bflow->blk, priv);
 | |
| 	if (ret < 0) {
 | |
| 		log_debug("scan vendor_boot failed: err=%d\n", ret);
 | |
| 		goto free_priv;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * Ignoring return code for the following configurations:
 | |
| 	 * these are not mandatory for booting.
 | |
| 	 */
 | |
| 	configure_serialno(bflow);
 | |
| 	configure_bootloader_version(bflow);
 | |
| 
 | |
| 	if (priv->boot_mode == ANDROID_BOOT_MODE_NORMAL) {
 | |
| 		ret = bootflow_cmdline_set_arg(bflow, "androidboot.force_normal_boot",
 | |
| 					       "1", false);
 | |
| 		if (ret < 0) {
 | |
| 			log_debug("normal_boot %d", ret);
 | |
| 			goto free_priv;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	bflow->state = BOOTFLOWST_READY;
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
|  free_priv:
 | |
| 	free(priv);
 | |
| 	bflow->bootmeth_priv = NULL;
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int android_read_file(struct udevice *dev, struct bootflow *bflow,
 | |
| 			     const char *file_path, ulong addr, ulong *sizep)
 | |
| {
 | |
| 	/*
 | |
| 	 * Reading individual files is not supported since we only
 | |
| 	 * operate on whole mmc devices (because we require multiple partitions)
 | |
| 	 */
 | |
| 	return log_msg_ret("Unsupported", -ENOSYS);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * read_slotted_partition() - Read a partition by appending a slot suffix
 | |
|  *
 | |
|  * Most modern Android devices use Seamless Updates, where each partition
 | |
|  * is duplicated. For example, the boot partition has boot_a and boot_b.
 | |
|  * For more information, see:
 | |
|  * https://source.android.com/docs/core/ota/ab
 | |
|  * https://source.android.com/docs/core/ota/ab/ab_implement
 | |
|  *
 | |
|  * @blk: Block device to read
 | |
|  * @name: Partition name to read
 | |
|  * @slot: Nul-terminated slot suffixed to partition name ("a\0" or "b\0")
 | |
|  * @addr: Address where the partition content is loaded into
 | |
|  * Return: 0 if OK, negative errno on failure.
 | |
|  */
 | |
| static int read_slotted_partition(struct blk_desc *desc, const char *const name,
 | |
| 				  const char slot[2], ulong addr)
 | |
| {
 | |
| 	struct disk_partition partition;
 | |
| 	char partname[PART_NAME_LEN];
 | |
| 	int ret;
 | |
| 	u32 n;
 | |
| 
 | |
| 	/* Ensure name fits in partname it should be: <name>_<slot>\0 */
 | |
| 	if (strlen(name) > (PART_NAME_LEN - 2 - 1))
 | |
| 		return log_msg_ret("name too long", -EINVAL);
 | |
| 
 | |
| 	sprintf(partname, "%s_%s", name, slot);
 | |
| 	ret = part_get_info_by_name(desc, partname, &partition);
 | |
| 	if (ret < 0)
 | |
| 		return log_msg_ret("part", ret);
 | |
| 
 | |
| 	n = blk_dread(desc, partition.start, partition.size, map_sysmem(addr, 0));
 | |
| 	if (n < partition.size)
 | |
| 		return log_msg_ret("part read", -EIO);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| #if CONFIG_IS_ENABLED(AVB_VERIFY)
 | |
| static int avb_append_commandline_arg(struct bootflow *bflow, char *arg)
 | |
| {
 | |
| 	char *key = strsep(&arg, "=");
 | |
| 	char *value = arg;
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = bootflow_cmdline_set_arg(bflow, key, value, false);
 | |
| 	if (ret < 0)
 | |
| 		return log_msg_ret("avb cmdline", ret);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int avb_append_commandline(struct bootflow *bflow, char *cmdline)
 | |
| {
 | |
| 	char *arg = strsep(&cmdline, " ");
 | |
| 	int ret;
 | |
| 
 | |
| 	while (arg) {
 | |
| 		ret = avb_append_commandline_arg(bflow, arg);
 | |
| 		if (ret < 0)
 | |
| 			return ret;
 | |
| 
 | |
| 		arg = strsep(&cmdline, " ");
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int run_avb_verification(struct bootflow *bflow)
 | |
| {
 | |
| 	struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
 | |
| 	struct android_priv *priv = bflow->bootmeth_priv;
 | |
| 	const char * const requested_partitions[] = {"boot", "vendor_boot"};
 | |
| 	struct AvbOps *avb_ops;
 | |
| 	AvbSlotVerifyResult result;
 | |
| 	AvbSlotVerifyData *out_data;
 | |
| 	enum avb_boot_state boot_state;
 | |
| 	char *extra_args;
 | |
| 	char slot_suffix[3];
 | |
| 	bool unlocked = false;
 | |
| 	int ret;
 | |
| 
 | |
| 	avb_ops = avb_ops_alloc(desc->devnum);
 | |
| 	if (!avb_ops)
 | |
| 		return log_msg_ret("avb ops", -ENOMEM);
 | |
| 
 | |
| 	sprintf(slot_suffix, "_%s", priv->slot);
 | |
| 
 | |
| 	ret = avb_ops->read_is_device_unlocked(avb_ops, &unlocked);
 | |
| 	if (ret != AVB_IO_RESULT_OK)
 | |
| 		return log_msg_ret("avb lock", -EIO);
 | |
| 
 | |
| 	result = avb_slot_verify(avb_ops,
 | |
| 				 requested_partitions,
 | |
| 				 slot_suffix,
 | |
| 				 unlocked,
 | |
| 				 AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE,
 | |
| 				 &out_data);
 | |
| 
 | |
| 	if (result != AVB_SLOT_VERIFY_RESULT_OK) {
 | |
| 		printf("Verification failed, reason: %s\n",
 | |
| 		       str_avb_slot_error(result));
 | |
| 		avb_slot_verify_data_free(out_data);
 | |
| 		return log_msg_ret("avb verify", -EIO);
 | |
| 	}
 | |
| 
 | |
| 	if (unlocked)
 | |
| 		boot_state = AVB_ORANGE;
 | |
| 	else
 | |
| 		boot_state = AVB_GREEN;
 | |
| 
 | |
| 	extra_args = avb_set_state(avb_ops, boot_state);
 | |
| 	if (extra_args) {
 | |
| 		/* extra_args will be modified after this. This is fine */
 | |
| 		ret = avb_append_commandline_arg(bflow, extra_args);
 | |
| 		if (ret < 0)
 | |
| 			goto free_out_data;
 | |
| 	}
 | |
| 
 | |
| 	ret = avb_append_commandline(bflow, out_data->cmdline);
 | |
| 	if (ret < 0)
 | |
| 		goto free_out_data;
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
|  free_out_data:
 | |
| 	if (out_data)
 | |
| 		avb_slot_verify_data_free(out_data);
 | |
| 
 | |
| 	return log_msg_ret("avb cmdline", ret);
 | |
| }
 | |
| #else
 | |
| static int run_avb_verification(struct bootflow *bflow)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	/* When AVB is unsupported, pass ORANGE state  */
 | |
| 	ret = bootflow_cmdline_set_arg(bflow,
 | |
| 				       "androidboot.verifiedbootstate",
 | |
| 				       "orange", false);
 | |
| 	if (ret < 0)
 | |
| 		return log_msg_ret("avb cmdline", ret);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| #endif /* AVB_VERIFY */
 | |
| 
 | |
| static int boot_android_normal(struct bootflow *bflow)
 | |
| {
 | |
| 	struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
 | |
| 	struct android_priv *priv = bflow->bootmeth_priv;
 | |
| 	int ret;
 | |
| 	ulong loadaddr = env_get_hex("loadaddr", 0);
 | |
| 	ulong vloadaddr = env_get_hex("vendor_boot_comp_addr_r", 0);
 | |
| 
 | |
| 	ret = run_avb_verification(bflow);
 | |
| 	if (ret < 0)
 | |
| 		return log_msg_ret("avb", ret);
 | |
| 
 | |
| 	/* Read slot once more to decrement counter from BCB */
 | |
| 	ret = android_read_slot_from_bcb(bflow, true);
 | |
| 	if (ret < 0)
 | |
| 		return log_msg_ret("read slot", ret);
 | |
| 
 | |
| 	ret = read_slotted_partition(desc, "boot", priv->slot, loadaddr);
 | |
| 	if (ret < 0)
 | |
| 		return log_msg_ret("read boot", ret);
 | |
| 
 | |
| 	ret = read_slotted_partition(desc, "vendor_boot", priv->slot, vloadaddr);
 | |
| 	if (ret < 0)
 | |
| 		return log_msg_ret("read vendor_boot", ret);
 | |
| 
 | |
| 	set_abootimg_addr(loadaddr);
 | |
| 	set_avendor_bootimg_addr(vloadaddr);
 | |
| 
 | |
| 	ret = bootm_boot_start(loadaddr, bflow->cmdline);
 | |
| 
 | |
| 	return log_msg_ret("boot", ret);
 | |
| }
 | |
| 
 | |
| static int boot_android_recovery(struct bootflow *bflow)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = boot_android_normal(bflow);
 | |
| 
 | |
| 	return log_msg_ret("boot", ret);
 | |
| }
 | |
| 
 | |
| static int boot_android_bootloader(struct bootflow *bflow)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = run_command("fastboot usb 0", 0);
 | |
| 	do_reset(NULL, 0, 0, NULL);
 | |
| 
 | |
| 	return log_msg_ret("boot", ret);
 | |
| }
 | |
| 
 | |
| static int android_boot(struct udevice *dev, struct bootflow *bflow)
 | |
| {
 | |
| 	struct android_priv *priv = bflow->bootmeth_priv;
 | |
| 	int ret;
 | |
| 
 | |
| 	switch (priv->boot_mode) {
 | |
| 	case ANDROID_BOOT_MODE_NORMAL:
 | |
| 		ret = boot_android_normal(bflow);
 | |
| 		break;
 | |
| 	case ANDROID_BOOT_MODE_RECOVERY:
 | |
| 		ret = boot_android_recovery(bflow);
 | |
| 		break;
 | |
| 	case ANDROID_BOOT_MODE_BOOTLOADER:
 | |
| 		ret = boot_android_bootloader(bflow);
 | |
| 		break;
 | |
| 	default:
 | |
| 		printf("ANDROID: Unknown boot mode %d. Running fastboot...\n",
 | |
| 		       priv->boot_mode);
 | |
| 		boot_android_bootloader(bflow);
 | |
| 		/* Tell we failed to boot since boot mode is unknown */
 | |
| 		ret = -EFAULT;
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int android_bootmeth_bind(struct udevice *dev)
 | |
| {
 | |
| 	struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev);
 | |
| 
 | |
| 	plat->desc = "Android boot";
 | |
| 	plat->flags = BOOTMETHF_ANY_PART;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static struct bootmeth_ops android_bootmeth_ops = {
 | |
| 	.check		= android_check,
 | |
| 	.read_bootflow	= android_read_bootflow,
 | |
| 	.read_file	= android_read_file,
 | |
| 	.boot		= android_boot,
 | |
| };
 | |
| 
 | |
| static const struct udevice_id android_bootmeth_ids[] = {
 | |
| 	{ .compatible = "u-boot,android" },
 | |
| 	{ }
 | |
| };
 | |
| 
 | |
| U_BOOT_DRIVER(bootmeth_android) = {
 | |
| 	.name		= "bootmeth_android",
 | |
| 	.id		= UCLASS_BOOTMETH,
 | |
| 	.of_match	= android_bootmeth_ids,
 | |
| 	.ops		= &android_bootmeth_ops,
 | |
| 	.bind		= android_bootmeth_bind,
 | |
| };
 |