diff options
35 files changed, 954 insertions, 114 deletions
diff --git a/Documentation/.gitignore b/Documentation/.gitignore index e74fec8693..0d20b6487c 100644 --- a/Documentation/.gitignore +++ b/Documentation/.gitignore @@ -1,2 +1 @@ -output *.pyc diff --git a/arch/arm/cpu/armv7/sunxi/psci.c b/arch/arm/cpu/armv7/sunxi/psci.c index f3e8f99a71..2c5d99e9ac 100644 --- a/arch/arm/cpu/armv7/sunxi/psci.c +++ b/arch/arm/cpu/armv7/sunxi/psci.c @@ -276,7 +276,7 @@ int __secure psci_cpu_on(u32 __always_unused unused, u32 mpidr, u32 pc, return ARM_PSCI_RET_SUCCESS; } -void __secure psci_cpu_off(void) +s32 __secure psci_cpu_off(void) { psci_cpu_off_common(); diff --git a/arch/arm/include/asm/system.h b/arch/arm/include/asm/system.h index aed2e3c51e..a1a5e35ef6 100644 --- a/arch/arm/include/asm/system.h +++ b/arch/arm/include/asm/system.h @@ -516,6 +516,21 @@ enum { */ void mmu_page_table_flush(unsigned long start, unsigned long stop); +#ifdef CONFIG_ARMV7_PSCI +void psci_arch_cpu_entry(void); +u32 psci_version(void); +s32 psci_features(u32 function_id, u32 psci_fid); +s32 psci_cpu_off(void); +s32 psci_cpu_on(u32 function_id, u32 target_cpu, u32 pc, + u32 context_id); +s32 psci_affinity_info(u32 function_id, u32 target_affinity, + u32 lowest_affinity_level); +u32 psci_migrate_info_type(void); +void psci_system_off(void); +void psci_system_reset(void); +s32 psci_features(u32 function_id, u32 psci_fid); +#endif + #endif /* __ASSEMBLY__ */ #define arch_align_stack(x) (x) diff --git a/arch/arm/mach-imx/mx7/psci-mx7.c b/arch/arm/mach-imx/mx7/psci-mx7.c index 34ba0a9307..c98d2e96af 100644 --- a/arch/arm/mach-imx/mx7/psci-mx7.c +++ b/arch/arm/mach-imx/mx7/psci-mx7.c @@ -298,7 +298,7 @@ __secure s32 psci_affinity_info(u32 __always_unused function_id, return psci_state[cpu]; } -__secure s32 psci_migrate_info_type(u32 function_id) +__secure u32 psci_migrate_info_type(void) { /* Trusted OS is either not present or does not require migration */ return 2; diff --git a/arch/arm/mach-stm32mp/psci.c b/arch/arm/mach-stm32mp/psci.c index 139bb09292..1d91b2d324 100644 --- a/arch/arm/mach-stm32mp/psci.c +++ b/arch/arm/mach-stm32mp/psci.c @@ -30,7 +30,7 @@ u8 psci_state[STM32MP1_PSCI_NR_CPUS] __secure_data = { PSCI_AFFINITY_LEVEL_ON, PSCI_AFFINITY_LEVEL_OFF}; -void __secure psci_set_state(int cpu, u8 state) +static inline void psci_set_state(int cpu, u8 state) { psci_state[cpu] = state; dsb(); @@ -67,7 +67,7 @@ void __secure psci_arch_cpu_entry(void) writel(0xFFFFFFFF, TAMP_BACKUP_MAGIC_NUMBER); } -int __secure psci_features(u32 function_id, u32 psci_fid) +s32 __secure psci_features(u32 function_id, u32 psci_fid) { switch (psci_fid) { case ARM_PSCI_0_2_FN_PSCI_VERSION: @@ -82,12 +82,12 @@ int __secure psci_features(u32 function_id, u32 psci_fid) return ARM_PSCI_RET_NI; } -unsigned int __secure psci_version(u32 function_id) +u32 __secure psci_version(void) { return ARM_PSCI_VER_1_0; } -int __secure psci_affinity_info(u32 function_id, u32 target_affinity, +s32 __secure psci_affinity_info(u32 function_id, u32 target_affinity, u32 lowest_affinity_level) { u32 cpu = target_affinity & MPIDR_AFF0; @@ -104,7 +104,7 @@ int __secure psci_affinity_info(u32 function_id, u32 target_affinity, return psci_state[cpu]; } -int __secure psci_migrate_info_type(u32 function_id) +u32 __secure psci_migrate_info_type(void) { /* * in Power_State_Coordination_Interface_PDD_v1_1_DEN0022D.pdf @@ -116,7 +116,7 @@ int __secure psci_migrate_info_type(u32 function_id) return 2; } -int __secure psci_cpu_on(u32 function_id, u32 target_cpu, u32 pc, +s32 __secure psci_cpu_on(u32 function_id, u32 target_cpu, u32 pc, u32 context_id) { u32 cpu = target_cpu & MPIDR_AFF0; @@ -161,7 +161,7 @@ int __secure psci_cpu_on(u32 function_id, u32 target_cpu, u32 pc, return ARM_PSCI_RET_SUCCESS; } -int __secure psci_cpu_off(u32 function_id) +s32 __secure psci_cpu_off(void) { u32 cpu; @@ -181,7 +181,7 @@ int __secure psci_cpu_off(u32 function_id) wfi(); } -void __secure psci_system_reset(u32 function_id) +void __secure psci_system_reset(void) { /* System reset */ writel(RCC_MP_GRSTCSETR_MPSYSRST, RCC_MP_GRSTCSETR); @@ -190,7 +190,7 @@ void __secure psci_system_reset(u32 function_id) wfi(); } -void __secure psci_system_off(u32 function_id) +void __secure psci_system_off(void) { /* System Off is not managed, waiting user power off * TODO: handle I2C write in PMIC Main Control register bit 0 = SWOFF diff --git a/arch/arm/mach-uniphier/arm32/psci.c b/arch/arm/mach-uniphier/arm32/psci.c index 3f67edf26e..ef35923f6a 100644 --- a/arch/arm/mach-uniphier/arm32/psci.c +++ b/arch/arm/mach-uniphier/arm32/psci.c @@ -130,7 +130,7 @@ void psci_arch_init(void) u32 uniphier_psci_holding_pen_release __secure_data = 0xffffffff; -int __secure psci_cpu_on(u32 function_id, u32 cpuid, u32 entry_point, +s32 __secure psci_cpu_on(u32 function_id, u32 cpuid, u32 entry_point, u32 context_id) { u32 cpu = cpuid & 0xff; @@ -155,7 +155,7 @@ int __secure psci_cpu_on(u32 function_id, u32 cpuid, u32 entry_point, return PSCI_RET_SUCCESS; } -void __secure psci_system_reset(u32 function_id) +void __secure psci_system_reset(void) { reset_cpu(0); } diff --git a/cmd/Kconfig b/cmd/Kconfig index 175c6ad9e3..9e66cc110d 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -735,7 +735,7 @@ config CMD_FASTBOOT Android devices. Fastboot requires either the network stack enabled or support for acting as a USB device. - See doc/README.android-fastboot for more information. + See doc/android/fastboot.txt for more information. config CMD_FDC bool "fdcboot - Boot from floppy device" @@ -1198,6 +1198,21 @@ config CMD_SETEXPR endmenu +menu "Android support commands" + +config CMD_AB_SELECT + bool "ab_select" + default n + depends on ANDROID_AB + help + On Android devices with more than one boot slot (multiple copies of + the kernel and system images) this provides a command to select which + slot should be used to boot from and register the boot attempt. This + is used by the new A/B update model where one slot is updated in the + background while running from the other slot. + +endmenu + if NET menuconfig CMD_NET diff --git a/cmd/Makefile b/cmd/Makefile index 0aa3741453..43a6b0ee21 100644 --- a/cmd/Makefile +++ b/cmd/Makefile @@ -12,6 +12,7 @@ obj-y += version.o # command obj-$(CONFIG_CMD_AES) += aes.o +obj-$(CONFIG_CMD_AB_SELECT) += ab_select.o obj-$(CONFIG_CMD_ADC) += adc.o obj-$(CONFIG_CMD_ARMFLASH) += armflash.o obj-y += blk_common.o diff --git a/cmd/ab_select.c b/cmd/ab_select.c new file mode 100644 index 0000000000..7c8f2ee8eb --- /dev/null +++ b/cmd/ab_select.c @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * Copyright (C) 2017 The Android Open Source Project + */ + +#include <android_ab.h> +#include <command.h> + +static int do_ab_select(cmd_tbl_t *cmdtp, int flag, int argc, + char * const argv[]) +{ + int ret; + struct blk_desc *dev_desc; + disk_partition_t part_info; + char slot[2]; + + if (argc != 4) + return CMD_RET_USAGE; + + /* Lookup the "misc" partition from argv[2] and argv[3] */ + if (part_get_info_by_dev_and_name_or_num(argv[2], argv[3], + &dev_desc, &part_info) < 0) { + return CMD_RET_FAILURE; + } + + ret = ab_select_slot(dev_desc, &part_info); + if (ret < 0) { + printf("Android boot failed, error %d.\n", ret); + return CMD_RET_FAILURE; + } + + /* Android standard slot names are 'a', 'b', ... */ + slot[0] = BOOT_SLOT_NAME(ret); + slot[1] = '\0'; + env_set(argv[1], slot); + printf("ANDROID: Booting slot: %s\n", slot); + return CMD_RET_SUCCESS; +} + +U_BOOT_CMD(ab_select, 4, 0, do_ab_select, + "Select the slot used to boot from and register the boot attempt.", + "<slot_var_name> <interface> <dev[:part|#part_name]>\n" + " - Load the slot metadata from the partition 'part' on\n" + " device type 'interface' instance 'dev' and store the active\n" + " slot in the 'slot_var_name' variable. This also updates the\n" + " Android slot metadata with a boot attempt, which can cause\n" + " successive calls to this function to return a different result\n" + " if the returned slot runs out of boot attempts.\n" + " - If 'part_name' is passed, preceded with a # instead of :, the\n" + " partition name whose label is 'part_name' will be looked up in\n" + " the partition table. This is commonly the \"misc\" partition.\n" +); @@ -24,17 +24,17 @@ static struct bootloader_message bcb = { { 0 } }; static int bcb_cmd_get(char *cmd) { - if (!strncmp(cmd, "load", sizeof("load"))) + if (!strcmp(cmd, "load")) return BCB_CMD_LOAD; - if (!strncmp(cmd, "set", sizeof("set"))) + if (!strcmp(cmd, "set")) return BCB_CMD_FIELD_SET; - if (!strncmp(cmd, "clear", sizeof("clear"))) + if (!strcmp(cmd, "clear")) return BCB_CMD_FIELD_CLEAR; - if (!strncmp(cmd, "test", sizeof("test"))) + if (!strcmp(cmd, "test")) return BCB_CMD_FIELD_TEST; - if (!strncmp(cmd, "store", sizeof("store"))) + if (!strcmp(cmd, "store")) return BCB_CMD_STORE; - if (!strncmp(cmd, "dump", sizeof("dump"))) + if (!strcmp(cmd, "dump")) return BCB_CMD_FIELD_DUMP; else return -1; @@ -46,9 +46,6 @@ static int bcb_is_misused(int argc, char *const argv[]) switch (cmd) { case BCB_CMD_LOAD: - if (argc != 3) - goto err; - break; case BCB_CMD_FIELD_SET: if (argc != 3) goto err; @@ -86,23 +83,23 @@ err: return -1; } -static int bcb_field_get(char *name, char **field, int *size) +static int bcb_field_get(char *name, char **fieldp, int *sizep) { - if (!strncmp(name, "command", sizeof("command"))) { - *field = bcb.command; - *size = sizeof(bcb.command); - } else if (!strncmp(name, "status", sizeof("status"))) { - *field = bcb.status; - *size = sizeof(bcb.status); - } else if (!strncmp(name, "recovery", sizeof("recovery"))) { - *field = bcb.recovery; - *size = sizeof(bcb.recovery); - } else if (!strncmp(name, "stage", sizeof("stage"))) { - *field = bcb.stage; - *size = sizeof(bcb.stage); - } else if (!strncmp(name, "reserved", sizeof("reserved"))) { - *field = bcb.reserved; - *size = sizeof(bcb.reserved); + if (!strcmp(name, "command")) { + *fieldp = bcb.command; + *sizep = sizeof(bcb.command); + } else if (!strcmp(name, "status")) { + *fieldp = bcb.status; + *sizep = sizeof(bcb.status); + } else if (!strcmp(name, "recovery")) { + *fieldp = bcb.recovery; + *sizep = sizeof(bcb.recovery); + } else if (!strcmp(name, "stage")) { + *fieldp = bcb.stage; + *sizep = sizeof(bcb.stage); + } else if (!strcmp(name, "reserved")) { + *fieldp = bcb.reserved; + *sizep = sizeof(bcb.reserved); } else { printf("Error: Unknown bcb field '%s'\n", name); return -1; @@ -111,8 +108,8 @@ static int bcb_field_get(char *name, char **field, int *size) return 0; } -static int -do_bcb_load(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) +static int do_bcb_load(cmd_tbl_t *cmdtp, int flag, int argc, + char * const argv[]) { struct blk_desc *desc; disk_partition_t info; @@ -122,28 +119,28 @@ do_bcb_load(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) ret = blk_get_device_by_str("mmc", argv[1], &desc); if (ret < 0) - goto err_1; + goto err_read_fail; part = simple_strtoul(argv[2], &endp, 0); if (*endp == '\0') { ret = part_get_info(desc, part, &info); if (ret) - goto err_1; + goto err_read_fail; } else { part = part_get_info_by_name(desc, argv[2], &info); if (part < 0) { ret = part; - goto err_1; + goto err_read_fail; } } cnt = DIV_ROUND_UP(sizeof(struct bootloader_message), info.blksz); if (cnt > info.size) - goto err_2; + goto err_too_small; if (blk_dread(desc, info.start, cnt, &bcb) != cnt) { ret = -EIO; - goto err_1; + goto err_read_fail; } bcb_dev = desc->devnum; @@ -151,10 +148,10 @@ do_bcb_load(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) debug("%s: Loaded from mmc %d:%d\n", __func__, bcb_dev, bcb_part); return CMD_RET_SUCCESS; -err_1: +err_read_fail: printf("Error: mmc %s:%s read failed (%d)\n", argv[1], argv[2], ret); goto err; -err_2: +err_too_small: printf("Error: mmc %s:%s too small!", argv[1], argv[2]); goto err; err: @@ -307,7 +304,8 @@ static int do_bcb(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) return CMD_RET_USAGE; if (bcb_is_misused(argc, argv)) { - /* We try to improve the user experience by reporting the + /* + * We try to improve the user experience by reporting the * root-cause of misusage, so don't return CMD_RET_USAGE, * since the latter prints out the full-blown help text */ diff --git a/cmd/part.c b/cmd/part.c index bfb6488b0f..653e13ced1 100644 --- a/cmd/part.c +++ b/cmd/part.c @@ -24,6 +24,7 @@ enum cmd_part_info { CMD_PART_INFO_START = 0, CMD_PART_INFO_SIZE, + CMD_PART_INFO_NUMBER }; static int do_part_uuid(int argc, char * const argv[]) @@ -149,6 +150,9 @@ static int do_part_info(int argc, char * const argv[], enum cmd_part_info param) case CMD_PART_INFO_SIZE: snprintf(buf, sizeof(buf), LBAF, info.size); break; + case CMD_PART_INFO_NUMBER: + snprintf(buf, sizeof(buf), "%d", part); + break; default: printf("** Unknown cmd_part_info value: %d\n", param); return 1; @@ -172,6 +176,11 @@ static int do_part_size(int argc, char * const argv[]) return do_part_info(argc, argv, CMD_PART_INFO_SIZE); } +static int do_part_number(int argc, char * const argv[]) +{ + return do_part_info(argc, argv, CMD_PART_INFO_NUMBER); +} + static int do_part(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) { if (argc < 2) @@ -185,6 +194,8 @@ static int do_part(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) return do_part_start(argc - 2, argv + 2); else if (!strcmp(argv[1], "size")) return do_part_size(argc - 2, argv + 2); + else if (!strcmp(argv[1], "number")) + return do_part_number(argc - 2, argv + 2); return CMD_RET_USAGE; } @@ -206,5 +217,8 @@ U_BOOT_CMD( " part can be either partition number or partition name\n" "part size <interface> <dev> <part> <varname>\n" " - set environment variable to the size of the partition (in blocks)\n" - " part can be either partition number or partition name" + " part can be either partition number or partition name\n" + "part number <interface> <dev> <part> <varname>\n" + " - set environment variable to the partition number using the partition name\n" + " part must be specified as partition name" ); diff --git a/common/Kconfig b/common/Kconfig index 4865a4dfc8..b556b59e9f 100644 --- a/common/Kconfig +++ b/common/Kconfig @@ -821,6 +821,16 @@ config UPDATE_TFTP_MSEC_MAX default 100 depends on UPDATE_TFTP +config ANDROID_AB + bool "Android A/B updates" + default n + help + If enabled, adds support for the new Android A/B update model. This + allows the bootloader to select which slot to boot from based on the + information provided by userspace via the Android boot_ctrl HAL. This + allows a bootloader to try a new version of the system but roll back + to previous version if the new one didn't boot all the way. + endmenu menu "Blob list" diff --git a/common/Makefile b/common/Makefile index c7e41ef307..302d8beaf3 100644 --- a/common/Makefile +++ b/common/Makefile @@ -107,6 +107,7 @@ endif endif obj-y += image.o +obj-$(CONFIG_ANDROID_AB) += android_ab.o obj-$(CONFIG_ANDROID_BOOT_IMAGE) += image-android.o obj-$(CONFIG_$(SPL_TPL_)OF_LIBFDT) += image-fdt.o obj-$(CONFIG_$(SPL_TPL_)FIT) += image-fit.o diff --git a/common/android_ab.c b/common/android_ab.c new file mode 100644 index 0000000000..05ffc6f4e5 --- /dev/null +++ b/common/android_ab.c @@ -0,0 +1,300 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * Copyright (C) 2017 The Android Open Source Project + */ +#include <common.h> +#include <android_ab.h> +#include <android_bootloader_message.h> +#include <linux/err.h> +#include <memalign.h> +#include <u-boot/crc.h> + +/** + * Compute the CRC-32 of the bootloader control struct. + * + * Only the bytes up to the crc32_le field are considered for the CRC-32 + * calculation. + * + * @param[in] abc bootloader control block + * + * @return crc32 sum + */ +static uint32_t ab_control_compute_crc(struct bootloader_control *abc) +{ + return crc32(0, (void *)abc, offsetof(typeof(*abc), crc32_le)); +} + +/** + * Initialize bootloader_control to the default value. + * + * It allows us to boot all slots in order from the first one. This value + * should be used when the bootloader message is corrupted, but not when + * a valid message indicates that all slots are unbootable. + * + * @param[in] abc bootloader control block + * + * @return 0 on success and a negative on error + */ +static int ab_control_default(struct bootloader_control *abc) +{ + int i; + const struct slot_metadata metadata = { + .priority = 15, + .tries_remaining = 7, + .successful_boot = 0, + .verity_corrupted = 0, + .reserved = 0 + }; + + if (!abc) + return -EFAULT; + + memcpy(abc->slot_suffix, "a\0\0\0", 4); + abc->magic = BOOT_CTRL_MAGIC; + abc->version = BOOT_CTRL_VERSION; + abc->nb_slot = NUM_SLOTS; + memset(abc->reserved0, 0, sizeof(abc->reserved0)); + for (i = 0; i < abc->nb_slot; ++i) + abc->slot_info[i] = metadata; + + memset(abc->reserved1, 0, sizeof(abc->reserved1)); + abc->crc32_le = ab_control_compute_crc(abc); + + return 0; +} + +/** + * Load the boot_control struct from disk into newly allocated memory. + * + * This function allocates and returns an integer number of disk blocks, + * based on the block size of the passed device to help performing a + * read-modify-write operation on the boot_control struct. + * The boot_control struct offset (2 KiB) must be a multiple of the device + * block size, for simplicity. + * + * @param[in] dev_desc Device where to read the boot_control struct from + * @param[in] part_info Partition in 'dev_desc' where to read from, normally + * the "misc" partition should be used + * @param[out] pointer to pointer to bootloader_control data + * @return 0 on success and a negative on error + */ +static int ab_control_create_from_disk(struct blk_desc *dev_desc, + const disk_partition_t *part_info, + struct bootloader_control **abc) +{ + ulong abc_offset, abc_blocks, ret; + + abc_offset = offsetof(struct bootloader_message_ab, slot_suffix); + if (abc_offset % part_info->blksz) { + log_err("ANDROID: Boot control block not block aligned.\n"); + return -EINVAL; + } + abc_offset /= part_info->blksz; + + abc_blocks = DIV_ROUND_UP(sizeof(struct bootloader_control), + part_info->blksz); + if (abc_offset + abc_blocks > part_info->size) { + log_err("ANDROID: boot control partition too small. Need at"); + log_err(" least %lu blocks but have %lu blocks.\n", + abc_offset + abc_blocks, part_info->size); + return -EINVAL; + } + *abc = malloc_cache_aligned(abc_blocks * part_info->blksz); + if (!*abc) + return -ENOMEM; + + ret = blk_dread(dev_desc, part_info->start + abc_offset, abc_blocks, + *abc); + if (IS_ERR_VALUE(ret)) { + log_err("ANDROID: Could not read from boot ctrl partition\n"); + free(*abc); + return -EIO; + } + + log_debug("ANDROID: Loaded ABC, %lu blocks\n", abc_blocks); + + return 0; +} + +/** + * Store the loaded boot_control block. + * + * Store back to the same location it was read from with + * ab_control_create_from_misc(). + * + * @param[in] dev_desc Device where we should write the boot_control struct + * @param[in] part_info Partition on the 'dev_desc' where to write + * @param[in] abc Pointer to the boot control struct and the extra bytes after + * it up to the nearest block boundary + * @return 0 on success and a negative on error + */ +static int ab_control_store(struct blk_desc *dev_desc, + const disk_partition_t *part_info, + struct bootloader_control *abc) +{ + ulong abc_offset, abc_blocks, ret; + + abc_offset = offsetof(struct bootloader_message_ab, slot_suffix) / + part_info->blksz; + abc_blocks = DIV_ROUND_UP(sizeof(struct bootloader_control), + part_info->blksz); + ret = blk_dwrite(dev_desc, part_info->start + abc_offset, abc_blocks, + abc); + if (IS_ERR_VALUE(ret)) { + log_err("ANDROID: Could not write back the misc partition\n"); + return -EIO; + } + + return 0; +} + +/** + * Compare two slots. + * + * The function determines slot which is should we boot from among the two. + * + * @param[in] a The first bootable slot metadata + * @param[in] b The second bootable slot metadata + * @return Negative if the slot "a" is better, positive of the slot "b" is + * better or 0 if they are equally good. + */ +static int ab_compare_slots(const struct slot_metadata *a, + const struct slot_metadata *b) +{ + /* Higher priority is better */ + if (a->priority != b->priority) + return b->priority - a->priority; + + /* Higher successful_boot value is better, in case of same priority */ + if (a->successful_boot != b->successful_boot) + return b->successful_boot - a->successful_boot; + + /* Higher tries_remaining is better to ensure round-robin */ + if (a->tries_remaining != b->tries_remaining) + return b->tries_remaining - a->tries_remaining; + + return 0; +} + +int ab_select_slot(struct blk_desc *dev_desc, disk_partition_t *part_info) +{ + struct bootloader_control *abc = NULL; + u32 crc32_le; + int slot, i, ret; + bool store_needed = false; + char slot_suffix[4]; + + ret = ab_control_create_from_disk(dev_desc, part_info, &abc); + if (ret < 0) { + /* + * This condition represents an actual problem with the code or + * the board setup, like an invalid partition information. + * Signal a repair mode and do not try to boot from either slot. + */ + return ret; + } + + crc32_le = ab_control_compute_crc(abc); + if (abc->crc32_le != crc32_le) { + log_err("ANDROID: Invalid CRC-32 (expected %.8x, found %.8x),", + crc32_le, abc->crc32_le); + log_err("re-initializing A/B metadata.\n"); + + ret = ab_control_default(abc); + if (ret < 0) { + free(abc); + return -ENODATA; + } + store_needed = true; + } + + if (abc->magic != BOOT_CTRL_MAGIC) { + log_err("ANDROID: Unknown A/B metadata: %.8x\n", abc->magic); + free(abc); + return -ENODATA; + } + + if (abc->version > BOOT_CTRL_VERSION) { + log_err("ANDROID: Unsupported A/B metadata version: %.8x\n", + abc->version); + free(abc); + return -ENODATA; + } + + /* + * At this point a valid boot control metadata is stored in abc, + * followed by other reserved data in the same block. We select a with + * the higher priority slot that + * - is not marked as corrupted and + * - either has tries_remaining > 0 or successful_boot is true. + * If the selected slot has a false successful_boot, we also decrement + * the tries_remaining until it eventually becomes unbootable because + * tries_remaining reaches 0. This mechanism produces a bootloader + * induced rollback, typically right after a failed update. + */ + + /* Safety check: limit the number of slots. */ + if (abc->nb_slot > ARRAY_SIZE(abc->slot_info)) { + abc->nb_slot = ARRAY_SIZE(abc->slot_info); + store_needed = true; + } + + slot = -1; + for (i = 0; i < abc->nb_slot; ++i) { + if (abc->slot_info[i].verity_corrupted || + !abc->slot_info[i].tries_remaining) { + log_debug("ANDROID: unbootable slot %d tries: %d, ", + i, abc->slot_info[i].tries_remaining); + log_debug("corrupt: %d\n", + abc->slot_info[i].verity_corrupted); + continue; + } + log_debug("ANDROID: bootable slot %d pri: %d, tries: %d, ", + i, abc->slot_info[i].priority, + abc->slot_info[i].tries_remaining); + log_debug("corrupt: %d, successful: %d\n", + abc->slot_info[i].verity_corrupted, + abc->slot_info[i].successful_boot); + + if (slot < 0 || + ab_compare_slots(&abc->slot_info[i], + &abc->slot_info[slot]) < 0) { + slot = i; + } + } + + if (slot >= 0 && !abc->slot_info[slot].successful_boot) { + log_err("ANDROID: Attempting slot %c, tries remaining %d\n", + BOOT_SLOT_NAME(slot), + abc->slot_info[slot].tries_remaining); + abc->slot_info[slot].tries_remaining--; + store_needed = true; + } + + if (slot >= 0) { + /* + * Legacy user-space requires this field to be set in the BCB. + * Newer releases load this slot suffix from the command line + * or the device tree. + */ + memset(slot_suffix, 0, sizeof(slot_suffix)); + slot_suffix[0] = BOOT_SLOT_NAME(slot); + if (memcmp(abc->slot_suffix, slot_suffix, + sizeof(slot_suffix))) { + memcpy(abc->slot_suffix, slot_suffix, + sizeof(slot_suffix)); + store_needed = true; + } + } + + if (store_needed) { + abc->crc32_le = ab_control_compute_crc(abc); + ab_control_store(dev_desc, part_info, abc); + } + free(abc); + + if (slot < 0) + return -EINVAL; + + return slot; +} diff --git a/common/image-android.c b/common/image-android.c index 8b0f6b3b8b..6c9568a655 100644 --- a/common/image-android.c +++ b/common/image-android.c @@ -52,6 +52,8 @@ int android_image_get_kernel(const struct andr_img_hdr *hdr, int verify, ulong *os_data, ulong *os_len) { u32 kernel_addr = android_image_get_kernel_addr(hdr); + const struct image_header *ihdr = (const struct image_header *) + ((uintptr_t)hdr + hdr->page_size); /* * Not all Android tools use the id field for signing the image with @@ -93,11 +95,19 @@ int android_image_get_kernel(const struct andr_img_hdr *hdr, int verify, env_set("bootargs", newbootargs); if (os_data) { - *os_data = (ulong)hdr; - *os_data += hdr->page_size; + if (image_get_magic(ihdr) == IH_MAGIC) { + *os_data = image_get_data(ihdr); + } else { + *os_data = (ulong)hdr; + *os_data += hdr->page_size; + } + } + if (os_len) { + if (image_get_magic(ihdr) == IH_MAGIC) + *os_len = image_get_data_size(ihdr); + else + *os_len = hdr->kernel_size; } - if (os_len) - *os_len = hdr->kernel_size; return 0; } @@ -131,7 +141,9 @@ ulong android_image_get_kcomp(const struct andr_img_hdr *hdr) { const void *p = (void *)((uintptr_t)hdr + hdr->page_size); - if (get_unaligned_le32(p) == LZ4F_MAGIC) + if (image_get_magic((image_header_t *)p) == IH_MAGIC) + return image_get_comp((image_header_t *)p); + else if (get_unaligned_le32(p) == LZ4F_MAGIC) return IH_COMP_LZ4; else return IH_COMP_NONE; diff --git a/common/menu.c b/common/menu.c index 0f0a29ac2e..7b66d199a9 100644 --- a/common/menu.c +++ b/common/menu.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0+ /* * Copyright 2010-2011 Calxeda, Inc. + * Copyright (c) 2019, NVIDIA CORPORATION. All rights reserved. */ #include <common.h> @@ -39,6 +40,7 @@ struct menu { char *(*item_choice)(void *); void *item_choice_data; struct list_head items; + int item_cnt; }; /* @@ -271,7 +273,7 @@ int menu_get_choice(struct menu *m, void **choice) if (!m || !choice) return -EINVAL; - if (!m->prompt) + if (!m->prompt || m->item_cnt == 1) return menu_default_choice(m, choice); return menu_interactive_choice(m, choice); @@ -323,6 +325,7 @@ int menu_item_add(struct menu *m, char *item_key, void *item_data) item->data = item_data; list_add_tail(&item->list, &m->items); + m->item_cnt++; return 1; } @@ -374,6 +377,7 @@ struct menu *menu_create(char *title, int timeout, int prompt, m->item_data_print = item_data_print; m->item_choice = item_choice; m->item_choice_data = item_choice_data; + m->item_cnt = 0; if (title) { m->title = strdup(title); diff --git a/common/spl/Kconfig b/common/spl/Kconfig index 5978fb2934..5d6da5db89 100644 --- a/common/spl/Kconfig +++ b/common/spl/Kconfig @@ -918,6 +918,20 @@ config SPL_SATA_SUPPORT expense and power consumption. This enables loading from SATA using a configured device. +config SPL_SATA_RAW_U_BOOT_USE_SECTOR + bool "SATA raw mode: by sector" + depends on SPL_SATA_SUPPORT + help + Use sector number for specifying U-Boot location on SATA disk in + raw mode. + +config SPL_SATA_RAW_U_BOOT_SECTOR + hex "Sector on the SATA disk to load U-Boot from" + depends on SPL_SATA_RAW_U_BOOT_USE_SECTOR + help + Sector on the SATA disk to load U-Boot from, when the SATA disk is being + used in raw mode. Units: SATA disk sectors (1 sector = 512 bytes). + config SPL_SERIAL_SUPPORT bool "Support serial" select SPL_PRINTF diff --git a/common/spl/spl_sata.c b/common/spl/spl_sata.c index f0af9f38d1..e108af0576 100644 --- a/common/spl/spl_sata.c +++ b/common/spl/spl_sata.c @@ -25,6 +25,37 @@ #define CONFIG_SPL_FS_LOAD_PAYLOAD_NAME "u-boot.img" #endif +#ifndef CONFIG_SPL_SATA_RAW_U_BOOT_SECTOR +/* Dummy value to make the compiler happy */ +#define CONFIG_SPL_SATA_RAW_U_BOOT_SECTOR 0x100 +#endif + +static int spl_sata_load_image_raw(struct spl_image_info *spl_image, + struct blk_desc *stor_dev, unsigned long sector) +{ + struct image_header *header; + unsigned long count; + u32 image_size_sectors; + int ret; + + header = spl_get_load_buffer(-sizeof(*header), stor_dev->blksz); + count = blk_dread(stor_dev, sector, 1, header); + if (count == 0) + return -EIO; + + ret = spl_parse_image_header(spl_image, header); + if (ret) + return ret; + + image_size_sectors = DIV_ROUND_UP(spl_image->size, stor_dev->blksz); + count = blk_dread(stor_dev, sector, image_size_sectors, + (void *)spl_image->load_addr); + if (count != image_size_sectors) + return -EIO; + + return 0; +} + static int spl_sata_load_image(struct spl_image_info *spl_image, struct spl_boot_device *bootdev) { @@ -59,6 +90,9 @@ static int spl_sata_load_image(struct spl_image_info *spl_image, err = spl_load_image_fat(spl_image, stor_dev, CONFIG_SYS_SATA_FAT_BOOT_PARTITION, CONFIG_SPL_FS_LOAD_PAYLOAD_NAME); + } else if (IS_ENABLED(CONFIG_SPL_SATA_RAW_U_BOOT_USE_SECTOR)) { + err = spl_sata_load_image_raw(spl_image, stor_dev, + CONFIG_SPL_SATA_RAW_U_BOOT_SECTOR); } } if (err) { diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index 61391a7acd..11cc097cd5 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -20,6 +20,7 @@ CONFIG_PRE_CON_BUF_ADDR=0xf0000 CONFIG_LOG_MAX_LEVEL=6 CONFIG_LOG_ERROR_RETURN=y CONFIG_DISPLAY_BOARDINFO_LATE=y +CONFIG_ANDROID_AB=y CONFIG_CMD_CPU=y CONFIG_CMD_LICENSE=y CONFIG_CMD_BOOTZ=y @@ -47,6 +48,7 @@ CONFIG_CMD_REMOTEPROC=y CONFIG_CMD_SPI=y CONFIG_CMD_USB=y CONFIG_CMD_AXI=y +CONFIG_CMD_AB_SELECT=y CONFIG_CMD_TFTPPUT=y CONFIG_CMD_TFTPSRV=y CONFIG_CMD_RARP=y diff --git a/disk/part.c b/disk/part.c index f14bc22b6d..7e84214731 100644 --- a/disk/part.c +++ b/disk/part.c @@ -674,6 +674,74 @@ int part_get_info_by_name(struct blk_desc *dev_desc, const char *name, return part_get_info_by_name_type(dev_desc, name, info, PART_TYPE_ALL); } +/** + * Get partition info from device number and partition name. + * + * Parse a device number and partition name string in the form of + * "device_num#partition_name", for example "0#misc". If the partition + * is found, sets dev_desc and part_info accordingly with the information + * of the partition with the given partition_name. + * + * @param[in] dev_iface Device interface + * @param[in] dev_part_str Input string argument, like "0#misc" + * @param[out] dev_desc Place to store the device description pointer + * @param[out] part_info Place to store the partition information + * @return 0 on success, or a negative on error + */ +static int part_get_info_by_dev_and_name(const char *dev_iface, + const char *dev_part_str, + struct blk_desc **dev_desc, + disk_partition_t *part_info) +{ + char *ep; + const char *part_str; + int dev_num; + + part_str = strchr(dev_part_str, '#'); + if (!part_str || part_str == dev_part_str) + return -EINVAL; + + dev_num = simple_strtoul(dev_part_str, &ep, 16); + if (ep != part_str) { + /* Not all the first part before the # was parsed. */ + return -EINVAL; + } + part_str++; + + *dev_desc = blk_get_dev(dev_iface, dev_num); + if (!*dev_desc) { + printf("Could not find %s %d\n", dev_iface, dev_num); + return -EINVAL; + } + if (part_get_info_by_name(*dev_desc, part_str, part_info) < 0) { + printf("Could not find \"%s\" partition\n", part_str); + return -EINVAL; + } + return 0; +} + +int part_get_info_by_dev_and_name_or_num(const char *dev_iface, + const char *dev_part_str, + struct blk_desc **dev_desc, + disk_partition_t *part_info) +{ + /* Split the part_name if passed as "$dev_num#part_name". */ + if (!part_get_info_by_dev_and_name(dev_iface, dev_part_str, + dev_desc, part_info)) + return 0; + /* + * Couldn't lookup by name, try looking up the partition description + * directly. + */ + if (blk_get_device_part_str(dev_iface, dev_part_str, + dev_desc, part_info, 1) < 0) { + printf("Couldn't find partition %s %s\n", + dev_iface, dev_part_str); + return -EINVAL; + } + return 0; +} + void part_set_generic_name(const struct blk_desc *dev_desc, int part_num, char *name) { diff --git a/doc/.gitignore b/doc/.gitignore new file mode 100644 index 0000000000..53752db253 --- /dev/null +++ b/doc/.gitignore @@ -0,0 +1 @@ +output diff --git a/doc/android/ab.txt b/doc/android/ab.txt new file mode 100644 index 0000000000..9f37ed5c58 --- /dev/null +++ b/doc/android/ab.txt @@ -0,0 +1,67 @@ +Android A/B updates +=================== + +Overview +-------- + +A/B system updates ensures modern approach for system update. This feature +allows one to use two sets (or more) of partitions referred to as slots +(normally slot A and slot B). The system runs from the current slot while the +partitions in the unused slot can be updated [1]. + +A/B enablement +-------------- + +The A/B updates support can be activated by specifying next options in +your board configuration file: + + CONFIG_ANDROID_AB=y + CONFIG_CMD_AB_SELECT=y + +The disk space on target device must be partitioned in a way so that each +partition which needs to be updated has two or more instances. The name of +each instance must be formed by adding suffixes: _a, _b, _c, etc. +For example: boot_a, boot_b, system_a, system_b, vendor_a, vendor_b. + +As a result you can use 'ab_select' command to ensure A/B boot process in your +boot script. This command analyzes and processes A/B metadata stored on a +special partition (e.g. "misc") and determines which slot should be used for +booting up. + +Command usage +------------- + + ab_select <slot_var_name> <interface> <dev[:part_number|#part_name]> + +for example: + + => ab_select slot_name mmc 1:4 + +or + + => ab_select slot_name mmc 1#misc + +Result: + + => printenv slot_name + slot_name=a + +Based on this slot information, the current boot partition should be defined, +and next kernel command line parameters should be generated: + + - androidboot.slot_suffix= + - root= + +For example: + + androidboot.slot_suffix=_a root=/dev/mmcblk1p12 + +A/B metadata is organized according to AOSP reference [2]. On the first system +start with A/B enabled, when 'misc' partition doesn't contain required data, +the default A/B metadata will be created and written to 'misc' partition. + +References +---------- + +[1] https://source.android.com/devices/tech/ota/ab +[2] bootable/recovery/bootloader_message/include/bootloader_message/bootloader_message.h diff --git a/doc/conf.py b/doc/conf.py index 168c31346b..0772fb6f0c 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -170,7 +170,7 @@ except ImportError: # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +html_logo = '../tools/logos/u-boot_logo.svg' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 diff --git a/doc/sphinx/kerneldoc.py b/doc/sphinx/kerneldoc.py index fbedcc3946..e536360de1 100644 --- a/doc/sphinx/kerneldoc.py +++ b/doc/sphinx/kerneldoc.py @@ -39,6 +39,8 @@ from docutils.statemachine import ViewList from docutils.parsers.rst import directives, Directive from sphinx.ext.autodoc import AutodocReporter +import kernellog + __version__ = '1.0' class KernelDocDirective(Directive): @@ -86,7 +88,8 @@ class KernelDocDirective(Directive): cmd += [filename] try: - env.app.verbose('calling kernel-doc \'%s\'' % (" ".join(cmd))) + kernellog.verbose(env.app, + 'calling kernel-doc \'%s\'' % (" ".join(cmd))) p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p.communicate() @@ -96,7 +99,8 @@ class KernelDocDirective(Directive): if p.returncode != 0: sys.stderr.write(err) - env.app.warn('kernel-doc \'%s\' failed with return code %d' % (" ".join(cmd), p.returncode)) + kernellog.warn(env.app, + 'kernel-doc \'%s\' failed with return code %d' % (" ".join(cmd), p.returncode)) return [nodes.error(None, nodes.paragraph(text = "kernel-doc missing"))] elif env.config.kerneldoc_verbosity > 0: sys.stderr.write(err) @@ -128,8 +132,8 @@ class KernelDocDirective(Directive): return node.children except Exception as e: # pylint: disable=W0703 - env.app.warn('kernel-doc \'%s\' processing failed with: %s' % - (" ".join(cmd), str(e))) + kernellog.warn(env.app, 'kernel-doc \'%s\' processing failed with: %s' % + (" ".join(cmd), str(e))) return [nodes.error(None, nodes.paragraph(text = "kernel-doc missing"))] def setup(app): diff --git a/doc/sphinx/kernellog.py b/doc/sphinx/kernellog.py new file mode 100644 index 0000000000..af924f51a7 --- /dev/null +++ b/doc/sphinx/kernellog.py @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Sphinx has deprecated its older logging interface, but the replacement +# only goes back to 1.6. So here's a wrapper layer to keep around for +# as long as we support 1.4. +# +import sphinx + +if sphinx.__version__[:3] >= '1.6': + UseLogging = True + from sphinx.util import logging + logger = logging.getLogger('kerneldoc') +else: + UseLogging = False + +def warn(app, message): + if UseLogging: + logger.warning(message) + else: + app.warn(message) + +def verbose(app, message): + if UseLogging: + logger.verbose(message) + else: + app.verbose(message) + + diff --git a/doc/sphinx/kfigure.py b/doc/sphinx/kfigure.py index b97228d2cc..fbfe6693bb 100644 --- a/doc/sphinx/kfigure.py +++ b/doc/sphinx/kfigure.py @@ -60,6 +60,8 @@ import sphinx from sphinx.util.nodes import clean_astext from six import iteritems +import kernellog + PY3 = sys.version_info[0] == 3 if PY3: @@ -171,20 +173,20 @@ def setupTools(app): This function is called once, when the builder is initiated. """ global dot_cmd, convert_cmd # pylint: disable=W0603 - app.verbose("kfigure: check installed tools ...") + kernellog.verbose(app, "kfigure: check installed tools ...") dot_cmd = which('dot') convert_cmd = which('convert') if dot_cmd: - app.verbose("use dot(1) from: " + dot_cmd) + kernellog.verbose(app, "use dot(1) from: " + dot_cmd) else: - app.warn("dot(1) not found, for better output quality install " - "graphviz from http://www.graphviz.org") + kernellog.warn(app, "dot(1) not found, for better output quality install " + "graphviz from http://www.graphviz.org") if convert_cmd: - app.verbose("use convert(1) from: " + convert_cmd) + kernellog.verbose(app, "use convert(1) from: " + convert_cmd) else: - app.warn( + kernellog.warn(app, "convert(1) not found, for SVG to PDF conversion install " "ImageMagick (https://www.imagemagick.org)") @@ -220,12 +222,13 @@ def convert_image(img_node, translator, src_fname=None): # in kernel builds, use 'make SPHINXOPTS=-v' to see verbose messages - app.verbose('assert best format for: ' + img_node['uri']) + kernellog.verbose(app, 'assert best format for: ' + img_node['uri']) if in_ext == '.dot': if not dot_cmd: - app.verbose("dot from graphviz not available / include DOT raw.") + kernellog.verbose(app, + "dot from graphviz not available / include DOT raw.") img_node.replace_self(file2literal(src_fname)) elif translator.builder.format == 'latex': @@ -252,7 +255,8 @@ def convert_image(img_node, translator, src_fname=None): if translator.builder.format == 'latex': if convert_cmd is None: - app.verbose("no SVG to PDF conversion available / include SVG raw.") + kernellog.verbose(app, + "no SVG to PDF conversion available / include SVG raw.") img_node.replace_self(file2literal(src_fname)) else: dst_fname = path.join(translator.builder.outdir, fname + '.pdf') @@ -265,18 +269,19 @@ def convert_image(img_node, translator, src_fname=None): _name = dst_fname[len(translator.builder.outdir) + 1:] if isNewer(dst_fname, src_fname): - app.verbose("convert: {out}/%s already exists and is newer" % _name) + kernellog.verbose(app, + "convert: {out}/%s already exists and is newer" % _name) else: ok = False mkdir(path.dirname(dst_fname)) if in_ext == '.dot': - app.verbose('convert DOT to: {out}/' + _name) + kernellog.verbose(app, 'convert DOT to: {out}/' + _name) ok = dot2format(app, src_fname, dst_fname) elif in_ext == '.svg': - app.verbose('convert SVG to: {out}/' + _name) + kernellog.verbose(app, 'convert SVG to: {out}/' + _name) ok = svg2pdf(app, src_fname, dst_fname) if not ok: @@ -305,7 +310,8 @@ def dot2format(app, dot_fname, out_fname): with open(out_fname, "w") as out: exit_code = subprocess.call(cmd, stdout = out) if exit_code != 0: - app.warn("Error #%d when calling: %s" % (exit_code, " ".join(cmd))) + kernellog.warn(app, + "Error #%d when calling: %s" % (exit_code, " ".join(cmd))) return bool(exit_code == 0) def svg2pdf(app, svg_fname, pdf_fname): @@ -322,7 +328,7 @@ def svg2pdf(app, svg_fname, pdf_fname): # use stdout and stderr from parent exit_code = subprocess.call(cmd) if exit_code != 0: - app.warn("Error #%d when calling: %s" % (exit_code, " ".join(cmd))) + kernellog.warn(app, "Error #%d when calling: %s" % (exit_code, " ".join(cmd))) return bool(exit_code == 0) @@ -415,15 +421,15 @@ def visit_kernel_render(self, node): app = self.builder.app srclang = node.get('srclang') - app.verbose('visit kernel-render node lang: "%s"' % (srclang)) + kernellog.verbose(app, 'visit kernel-render node lang: "%s"' % (srclang)) tmp_ext = RENDER_MARKUP_EXT.get(srclang, None) if tmp_ext is None: - app.warn('kernel-render: "%s" unknown / include raw.' % (srclang)) + kernellog.warn(app, 'kernel-render: "%s" unknown / include raw.' % (srclang)) return if not dot_cmd and tmp_ext == '.dot': - app.verbose("dot from graphviz not available / include raw.") + kernellog.verbose(app, "dot from graphviz not available / include raw.") return literal_block = node[0] diff --git a/doc/uImage.FIT/signature.txt b/doc/uImage.FIT/signature.txt index c9b1802686..eee06517fa 100644 --- a/doc/uImage.FIT/signature.txt +++ b/doc/uImage.FIT/signature.txt @@ -216,7 +216,7 @@ As an example, consider this FIT: kernel = "kernel-1"; fdt = "fdt-1"; }; - conf-1 { + conf-2 { kernel = "kernel-2"; fdt = "fdt-2"; }; @@ -232,7 +232,7 @@ configuration 3 with kernel 1 and fdt 2: kernel = "kernel-1"; fdt = "fdt-1"; }; - conf-1 { + conf-2 { kernel = "kernel-2"; fdt = "fdt-2"; }; @@ -337,6 +337,7 @@ WARNING: When relying on signed FIT images with required signature check the legacy image format is default disabled by not defining CONFIG_LEGACY_IMAGE_FORMAT + Testing ------- An easy way to test signing and verification is to use the test script @@ -349,6 +350,8 @@ A sample run is show below: $ make O=sandbox sandbox_config $ make O=sandbox $ O=sandbox ./test/vboot/vboot_test.sh + + Simple Verified Boot Test ========================= diff --git a/include/android_ab.h b/include/android_ab.h new file mode 100644 index 0000000000..810906d22b --- /dev/null +++ b/include/android_ab.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (C) 2017 The Android Open Source Project + */ + +#ifndef __ANDROID_AB_H +#define __ANDROID_AB_H + +#include <common.h> + +/* Android standard boot slot names are 'a', 'b', 'c', ... */ +#define BOOT_SLOT_NAME(slot_num) ('a' + (slot_num)) + +/* Number of slots */ +#define NUM_SLOTS 2 + +/** + * Select the slot where to boot from. + * + * On Android devices with more than one boot slot (multiple copies of the + * kernel and system images) selects which slot should be used to boot from and + * registers the boot attempt. This is used in by the new A/B update model where + * one slot is updated in the background while running from the other slot. If + * the selected slot did not successfully boot in the past, a boot attempt is + * registered before returning from this function so it isn't selected + * indefinitely. + * + * @param[in] dev_desc Place to store the device description pointer + * @param[in] part_info Place to store the partition information + * @return The slot number (>= 0) on success, or a negative on error + */ +int ab_select_slot(struct blk_desc *dev_desc, disk_partition_t *part_info); + +#endif /* __ANDROID_AB_H */ diff --git a/include/config_distro_bootcmd.h b/include/config_distro_bootcmd.h index 26e61ef196..3570a32dff 100644 --- a/include/config_distro_bootcmd.h +++ b/include/config_distro_bootcmd.h @@ -254,11 +254,11 @@ #endif #if defined(CONFIG_DM_PCI) -#define BOOTENV_RUN_NET_PCI_ENUM "run boot_net_pci_enum; " +#define BOOTENV_RUN_PCI_ENUM "run boot_pci_enum; " #define BOOTENV_SHARED_PCI \ - "boot_net_pci_enum=pci enum\0" + "boot_pci_enum=pci enum\0" #else -#define BOOTENV_RUN_NET_PCI_ENUM +#define BOOTENV_RUN_PCI_ENUM #define BOOTENV_SHARED_PCI #endif @@ -281,10 +281,24 @@ #endif #ifdef CONFIG_CMD_VIRTIO -#define BOOTENV_SHARED_VIRTIO BOOTENV_SHARED_BLKDEV(virtio) +#define BOOTENV_RUN_VIRTIO_INIT "run virtio_init; " +#define BOOTENV_SET_VIRTIO_NEED_INIT "virtio_need_init=; " +#define BOOTENV_SHARED_VIRTIO \ + "virtio_init=" \ + "if ${virtio_need_init}; then " \ + "virtio_need_init=false; " \ + "virtio scan; " \ + "fi\0" \ + \ + "virtio_boot=" \ + BOOTENV_RUN_PCI_ENUM \ + BOOTENV_RUN_VIRTIO_INIT \ + BOOTENV_SHARED_BLKDEV_BODY(virtio) #define BOOTENV_DEV_VIRTIO BOOTENV_DEV_BLKDEV #define BOOTENV_DEV_NAME_VIRTIO BOOTENV_DEV_NAME_BLKDEV #else +#define BOOTENV_RUN_VIRTIO_INIT +#define BOOTENV_SET_VIRTIO_NEED_INIT #define BOOTENV_SHARED_VIRTIO #define BOOTENV_DEV_VIRTIO \ BOOT_TARGET_DEVICES_references_VIRTIO_without_CONFIG_CMD_VIRTIO @@ -350,7 +364,7 @@ #define BOOTENV_DEV_DHCP(devtypeu, devtypel, instance) \ "bootcmd_dhcp=" \ BOOTENV_RUN_NET_USB_START \ - BOOTENV_RUN_NET_PCI_ENUM \ + BOOTENV_RUN_PCI_ENUM \ "if dhcp ${scriptaddr} ${boot_script_dhcp}; then " \ "source ${scriptaddr}; " \ "fi;" \ @@ -369,7 +383,7 @@ #define BOOTENV_DEV_PXE(devtypeu, devtypel, instance) \ "bootcmd_pxe=" \ BOOTENV_RUN_NET_USB_START \ - BOOTENV_RUN_NET_PCI_ENUM \ + BOOTENV_RUN_PCI_ENUM \ "dhcp; " \ "if pxe get; then " \ "pxe boot; " \ @@ -465,6 +479,7 @@ "distro_bootcmd=" BOOTENV_SET_SCSI_NEED_INIT \ BOOTENV_SET_NVME_NEED_INIT \ BOOTENV_SET_IDE_NEED_INIT \ + BOOTENV_SET_VIRTIO_NEED_INIT \ "for target in ${boot_targets}; do " \ "run bootcmd_${target}; " \ "done\0" diff --git a/include/environment/ti/boot.h b/include/environment/ti/boot.h index 05bdbbc23e..54e9b2de4d 100644 --- a/include/environment/ti/boot.h +++ b/include/environment/ti/boot.h @@ -23,6 +23,18 @@ #define VBMETA_PART "" #endif +#if defined(CONFIG_CMD_AB_SELECT) +#define COMMON_PARTS \ + "name=boot_a,size=20M,uuid=${uuid_gpt_boot_a};" \ + "name=boot_b,size=20M,uuid=${uuid_gpt_boot_b};" \ + "name=system_a,size=1024M,uuid=${uuid_gpt_system_a};" \ + "name=system_b,size=1024M,uuid=${uuid_gpt_system_b};" +#else +#define COMMON_PARTS \ + "name=boot,size=20M,uuid=${uuid_gpt_boot};" \ + "name=system,size=1024M,uuid=${uuid_gpt_system};" +#endif + #ifndef PARTS_DEFAULT /* Define the default GPT table for eMMC */ #define PARTS_DEFAULT \ @@ -38,8 +50,7 @@ "name=uboot-env,start=2432K,size=256K,uuid=${uuid_gpt_reserved};" \ "name=misc,size=128K,uuid=${uuid_gpt_misc};" \ "name=recovery,size=40M,uuid=${uuid_gpt_recovery};" \ - "name=boot,size=10M,uuid=${uuid_gpt_boot};" \ - "name=system,size=1024M,uuid=${uuid_gpt_system};" \ + COMMON_PARTS \ "name=vendor,size=256M,uuid=${uuid_gpt_vendor};" \ VBMETA_PART \ "name=userdata,size=-,uuid=${uuid_gpt_userdata}" @@ -58,6 +69,35 @@ #define AVB_VERIFY_CMD "" #endif +#define CONTROL_PARTITION "misc" + +#if defined(CONFIG_CMD_AB_SELECT) +#define AB_SELECT \ + "if part number mmc 1 " CONTROL_PARTITION " control_part_number; " \ + "then " \ + "echo " CONTROL_PARTITION \ + " partition number:${control_part_number};" \ + "ab_select slot_name mmc ${mmcdev}:${control_part_number};" \ + "else " \ + "echo " CONTROL_PARTITION " partition not found;" \ + "exit;" \ + "fi;" \ + "setenv slot_suffix _${slot_name};" \ + "if part number mmc ${mmcdev} system${slot_suffix} " \ + "system_part_number; then " \ + "setenv bootargs_ab " \ + "ro root=/dev/mmcblk${mmcdev}p${system_part_number} " \ + "rootwait init=/init skip_initramfs " \ + "androidboot.slot_suffix=${slot_suffix};" \ + "echo A/B cmdline addition: ${bootargs_ab};" \ + "setenv bootargs ${bootargs} ${bootargs_ab};" \ + "else " \ + "echo system${slot_suffix} partition not found;" \ + "fi;" +#else +#define AB_SELECT "" +#endif + #define DEFAULT_COMMON_BOOT_TI_ARGS \ "console=" CONSOLEDEV ",115200n8\0" \ "fdtfile=undefined\0" \ @@ -86,10 +126,16 @@ "mmc dev $mmcdev; " \ "mmc rescan; " \ AVB_VERIFY_CHECK \ - "part start mmc ${mmcdev} boot boot_start; " \ - "part size mmc ${mmcdev} boot boot_size; " \ - "mmc read ${loadaddr} ${boot_start} ${boot_size}; " \ - "bootm ${loadaddr}#${fdtfile};\0 " + AB_SELECT \ + "if part start mmc ${mmcdev} boot${slot_suffix} boot_start; " \ + "then " \ + "part size mmc ${mmcdev} boot${slot_suffix} " \ + "boot_size; " \ + "mmc read ${loadaddr} ${boot_start} ${boot_size}; " \ + "bootm ${loadaddr}#${fdtfile}; " \ + "else " \ + "echo boot${slot_suffix} partition not found; " \ + "fi;\0" #ifdef CONFIG_OMAP54XX diff --git a/include/part.h b/include/part.h index ebca546db5..0b5cf3d5e8 100644 --- a/include/part.h +++ b/include/part.h @@ -202,6 +202,27 @@ int part_get_info_by_name(struct blk_desc *dev_desc, const char *name, disk_partition_t *info); /** + * Get partition info from dev number + part name, or dev number + part number. + * + * Parse a device number and partition description (either name or number) + * in the form of device number plus partition name separated by a "#" + * (like "device_num#partition_name") or a device number plus a partition number + * separated by a ":". For example both "0#misc" and "0:1" can be valid + * partition descriptions for a given interface. If the partition is found, sets + * dev_desc and part_info accordingly with the information of the partition. + * + * @param[in] dev_iface Device interface + * @param[in] dev_part_str Input partition description, like "0#misc" or "0:1" + * @param[out] dev_desc Place to store the device description pointer + * @param[out] part_info Place to store the partition information + * @return 0 on success, or a negative on error + */ +int part_get_info_by_dev_and_name_or_num(const char *dev_iface, + const char *dev_part_str, + struct blk_desc **dev_desc, + disk_partition_t *part_info); + +/** * part_set_generic_name() - create generic partition like hda1 or sdb2 * * Helper function for partition tables, which don't hold partition names diff --git a/include/remoteproc.h b/include/remoteproc.h index c29c0867bc..4987194905 100644 --- a/include/remoteproc.h +++ b/include/remoteproc.h @@ -130,7 +130,7 @@ struct dm_rproc_ops { /* Accessor */ #define rproc_get_ops(dev) ((struct dm_rproc_ops *)(dev)->driver->ops) -#ifdef CONFIG_REMOTEPROC +#if CONFIG_IS_ENABLED(REMOTEPROC) /** * rproc_init() - Initialize all bound remote proc devices * @return 0 if all ok, else appropriate error value. diff --git a/scripts/kernel-doc b/scripts/kernel-doc index 3cb6259182..516cf1db32 100755 --- a/scripts/kernel-doc +++ b/scripts/kernel-doc @@ -212,7 +212,7 @@ my $anon_struct_union = 0; my $type_constant = '\b``([^\`]+)``\b'; my $type_constant2 = '\%([-_\w]+)'; my $type_func = '(\w+)\(\)'; -my $type_param = '\@(\w*(\.\w+)*(\.\.\.)?)'; +my $type_param = '\@(\w*((\.\w+)|(->\w+))*(\.\.\.)?)'; my $type_fp_param = '\@(\w+)\(\)'; # Special RST handling for func ptr params my $type_env = '(\$\w+)'; my $type_enum = '\&(enum\s*([_\w]+))'; @@ -1062,7 +1062,7 @@ sub dump_struct($$) { my $x = shift; my $file = shift; - if ($x =~ /(struct|union)\s+(\w+)\s*{(.*)}/) { + if ($x =~ /(struct|union)\s+(\w+)\s*\{(.*)\}(\s*(__packed|__aligned|__attribute__\s*\(\([a-z0-9,_\s\(\)]*\)\)))*/) { my $decl_type = $1; $declaration_name = $2; my $members = $3; @@ -1073,8 +1073,9 @@ sub dump_struct($$) { # strip comments: $members =~ s/\/\*.*?\*\///gos; # strip attributes - $members =~ s/__attribute__\s*\(\([a-z,_\*\s\(\)]*\)\)//i; - $members =~ s/__aligned\s*\([^;]*\)//gos; + $members =~ s/\s*__attribute__\s*\(\([a-z0-9,_\*\s\(\)]*\)\)//gi; + $members =~ s/\s*__aligned\s*\([^;]*\)//gos; + $members =~ s/\s*__packed\s*//gos; $members =~ s/\s*CRYPTO_MINALIGN_ATTR//gos; # replace DECLARE_BITMAP $members =~ s/DECLARE_BITMAP\s*\(([^,)]+),\s*([^,)]+)\)/unsigned long $1\[BITS_TO_LONGS($2)\]/gos; @@ -1148,20 +1149,20 @@ sub dump_struct($$) { } } } - $members =~ s/(struct|union)([^\{\};]+)\{([^\{\}]*)}([^\{\}\;]*)\;/$newmember/; + $members =~ s/(struct|union)([^\{\};]+)\{([^\{\}]*)\}([^\{\}\;]*)\;/$newmember/; } # Ignore other nested elements, like enums - $members =~ s/({[^\{\}]*})//g; + $members =~ s/(\{[^\{\}]*\})//g; create_parameterlist($members, ';', $file, $declaration_name); check_sections($file, $declaration_name, $decl_type, $sectcheck, $struct_actual); # Adjust declaration for better display - $declaration =~ s/([{;])/$1\n/g; - $declaration =~ s/}\s+;/};/g; + $declaration =~ s/([\{;])/$1\n/g; + $declaration =~ s/\}\s+;/};/g; # Better handle inlined enums - do {} while ($declaration =~ s/(enum\s+{[^}]+),([^\n])/$1,\n$2/); + do {} while ($declaration =~ s/(enum\s+\{[^\}]+),([^\n])/$1,\n$2/); my @def_args = split /\n/, $declaration; my $level = 1; @@ -1171,12 +1172,12 @@ sub dump_struct($$) { $clause =~ s/\s+$//; $clause =~ s/\s+/ /; next if (!$clause); - $level-- if ($clause =~ m/(})/ && $level > 1); + $level-- if ($clause =~ m/(\})/ && $level > 1); if (!($clause =~ m/^\s*#/)) { $declaration .= "\t" x $level; } $declaration .= "\t" . $clause . "\n"; - $level++ if ($clause =~ m/({)/ && !($clause =~m/}/)); + $level++ if ($clause =~ m/(\{)/ && !($clause =~m/\}/)); } output_declaration($declaration_name, 'struct', @@ -1244,7 +1245,7 @@ sub dump_enum($$) { # strip #define macros inside enums $x =~ s@#\s*((define|ifdef)\s+|endif)[^;]*;@@gos; - if ($x =~ /enum\s+(\w+)\s*{(.*)}/) { + if ($x =~ /enum\s+(\w+)\s*\{(.*)\}/) { $declaration_name = $1; my $members = $2; my %_members; @@ -1381,7 +1382,7 @@ sub create_parameterlist($$$$) { } elsif ($arg =~ m/\(.+\)\s*\(/) { # pointer-to-function $arg =~ tr/#/,/; - $arg =~ m/[^\(]+\(\*?\s*([\w\.]*)\s*\)/; + $arg =~ m/[^\(]+\([\w\s]*\*?\s*([\w\.]*)\s*\)/; $param = $1; $type = $arg; $type =~ s/([^\(]+\(\*?)\s*$param/$1/; @@ -1473,7 +1474,7 @@ sub push_parameter($$$$) { if (!defined $parameterdescs{$param} && $param !~ /^#/) { $parameterdescs{$param} = $undescribed; - if (show_warnings($type, $declaration_name)) { + if (show_warnings($type, $declaration_name) && $param !~ /\./) { print STDERR "${file}:$.: warning: Function parameter or member '$param' not described in '$declaration_name'\n"; ++$warnings; @@ -1785,7 +1786,7 @@ sub process_proto_type($$) { } while (1) { - if ( $x =~ /([^{};]*)([{};])(.*)/ ) { + if ( $x =~ /([^\{\};]*)([\{\};])(.*)/ ) { if( length $prototype ) { $prototype .= " " } @@ -1904,13 +1905,13 @@ sub process_name($$) { ++$warnings; } - if ($identifier =~ m/^struct/) { + if ($identifier =~ m/^struct\b/) { $decl_type = 'struct'; - } elsif ($identifier =~ m/^union/) { + } elsif ($identifier =~ m/^union\b/) { $decl_type = 'union'; - } elsif ($identifier =~ m/^enum/) { + } elsif ($identifier =~ m/^enum\b/) { $decl_type = 'enum'; - } elsif ($identifier =~ m/^typedef/) { + } elsif ($identifier =~ m/^typedef\b/) { $decl_type = 'typedef'; } else { $decl_type = 'function'; diff --git a/test/py/tests/test_android/test_ab.py b/test/py/tests/test_android/test_ab.py new file mode 100644 index 0000000000..c79cb07fda --- /dev/null +++ b/test/py/tests/test_android/test_ab.py @@ -0,0 +1,75 @@ +# SPDX-License-Identifier: GPL-2.0 +# (C) Copyright 2018 Texas Instruments, <www.ti.com> + +# Test A/B update commands. + +import os +import pytest +import u_boot_utils + +class ABTestDiskImage(object): + """Disk Image used by the A/B tests.""" + + def __init__(self, u_boot_console): + """Initialize a new ABTestDiskImage object. + + Args: + u_boot_console: A U-Boot console. + + Returns: + Nothing. + """ + + filename = 'test_ab_disk_image.bin' + + persistent = u_boot_console.config.persistent_data_dir + '/' + filename + self.path = u_boot_console.config.result_dir + '/' + filename + + with u_boot_utils.persistent_file_helper(u_boot_console.log, persistent): + if os.path.exists(persistent): + u_boot_console.log.action('Disk image file ' + persistent + + ' already exists') + else: + u_boot_console.log.action('Generating ' + persistent) + fd = os.open(persistent, os.O_RDWR | os.O_CREAT) + os.ftruncate(fd, 524288) + os.close(fd) + cmd = ('sgdisk', persistent) + u_boot_utils.run_and_log(u_boot_console, cmd) + + cmd = ('sgdisk', '--new=1:64:512', '--change-name=1:misc', + persistent) + u_boot_utils.run_and_log(u_boot_console, cmd) + cmd = ('sgdisk', '--load-backup=' + persistent) + u_boot_utils.run_and_log(u_boot_console, cmd) + + cmd = ('cp', persistent, self.path) + u_boot_utils.run_and_log(u_boot_console, cmd) + +di = None +@pytest.fixture(scope='function') +def ab_disk_image(u_boot_console): + global di + if not di: + di = ABTestDiskImage(u_boot_console) + return di + +@pytest.mark.boardspec('sandbox') +@pytest.mark.buildconfigspec('android_ab') +@pytest.mark.buildconfigspec('cmd_ab_select') +@pytest.mark.requiredtool('sgdisk') +def test_ab(ab_disk_image, u_boot_console): + """Test the 'ab_select' command.""" + + u_boot_console.run_command('host bind 0 ' + ab_disk_image.path) + + output = u_boot_console.run_command('ab_select slot_name host 0#misc') + assert 're-initializing A/B metadata' in output + assert 'Attempting slot a, tries remaining 7' in output + output = u_boot_console.run_command('printenv slot_name') + assert 'slot_name=a' in output + + output = u_boot_console.run_command('ab_select slot_name host 0:1') + assert 'Attempting slot b, tries remaining 7' in output + output = u_boot_console.run_command('printenv slot_name') + assert 'slot_name=b' in output diff --git a/test/py/tests/test_avb.py b/test/py/tests/test_avb.py index 2bb75ed6e2..8132423435 100644 --- a/test/py/tests/test_avb.py +++ b/test/py/tests/test_avb.py @@ -8,7 +8,7 @@ This tests Android Verified Boot 2.0 support in U-boot: For additional details about how to build proper vbmeta partition -check doc/README.avb2 +check doc/android/avb2.txt For configuration verification: - Corrupt boot partition and check for failure |