| /* |
| * Copyright (c) 2018 The Fuchsia Authors |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| |
| #include <asm/arch/secure_apb.h> |
| #include <asm/arch/timer.h> |
| #include <command.h> |
| #include <asm/arch/reboot.h> |
| #include <asm/arch/secure_apb.h> |
| #include <asm/io.h> |
| #include <common.h> |
| #include <dm/uclass.h> |
| #include <fs.h> |
| #include <version.h> |
| #include <wdt.h> |
| #include <zbi/zbi.h> |
| #include <zircon/boot/bootfs.h> |
| #include <zircon/boot/image.h> |
| #include <zircon_uboot/boot_args.h> |
| #include <zircon_uboot/vboot.h> |
| #include <zircon_uboot/zircon.h> |
| |
| #include "factory.h" |
| |
| // bitmask for determining whether the RNG_USR_DATA register is ready. |
| // // This mask should be applied to the RNG_USR_STS register. |
| // // 0: The RNG_USR_DATA register is not ready to be read from. |
| // // 1: The RNG_USR_DATA register is ready to be read from. |
| #define USR_RAND32_RDY 0x1 |
| |
| #define PDEV_VID_GOOGLE 3 |
| #define PDEV_PID_SHERLOCK 5 |
| |
| #define NVRAM_LENGTH (8 * 1024) |
| |
| // Size of the CMDLINE entropy string. |
| #define CMDLINE_ENTROPY_SIZE 1024 |
| |
| // Random bits to pass to zircon. |
| #define CMDLINE_ENTROPY_BITS 256 |
| |
| // Deadline for time waiting for the RNG_USR_STS register to be ready. |
| // This is a very generous value, given that after reading from |
| // the hw rng, we should expect it to be available after |
| // HW_RNG_RESEEDING_INTERVAL_MICROS. |
| #define ENTROPY_COLLECTION_DEADLINE_MICROS 100000 |
| |
| // HW RNG is reseeded every 40 microseconds. |
| #define HW_RNG_RESEEDING_INTERVAL_MICROS 40 |
| |
| #define ENTROPY_BITS_PER_CHAR 4 |
| |
| static const char zircon_entropy_arg[] = "kernel.entropy-mixin="; |
| static char entropy_cmdline[CMDLINE_ENTROPY_SIZE] = { 0 }; |
| |
| _Static_assert(CMDLINE_ENTROPY_BITS % 32 == 0, |
| "Requested entropy must be a multiple of 32"); |
| |
| _Static_assert((CMDLINE_ENTROPY_BITS / ENTROPY_BITS_PER_CHAR) + |
| sizeof(zircon_entropy_arg) < |
| CMDLINE_ENTROPY_SIZE, |
| "Requested entropy doesn't fit in cmdline."); |
| |
| static const zbi_mem_range_t mem_config[] = { |
| { |
| .type = ZBI_MEM_RANGE_RAM, |
| .length = 0x80000000, // 2 GB |
| }, |
| { |
| .type = ZBI_MEM_RANGE_PERIPHERAL, |
| .paddr = 0xf5800000, |
| .length = 0x0a800000, |
| }, |
| /* secmon_reserved:linux,secmon */ |
| { |
| .type = ZBI_MEM_RANGE_RESERVED, |
| .paddr = 0x05000000, |
| .length = 0x2400000, |
| }, |
| /* logo_reserved:linux,meson-fb */ |
| { |
| .type = ZBI_MEM_RANGE_RESERVED, |
| .paddr = 0x76800000, |
| .length = 0x800000, |
| }, |
| /* linux,usable-memory */ |
| { |
| .type = ZBI_MEM_RANGE_RESERVED, |
| .paddr = 0x00000000, |
| .length = 0x100000, |
| }, |
| }; |
| |
| static const dcfg_simple_t uart_driver = { |
| .mmio_phys = 0xff803000, |
| .irq = 225, |
| }; |
| |
| static const dcfg_arm_gicv2_driver_t gicv2_driver = { |
| .mmio_phys = 0xffc00000, |
| .gicd_offset = 0x1000, |
| .gicc_offset = 0x2000, |
| .gich_offset = 0x4000, |
| .gicv_offset = 0x6000, |
| .ipi_base = 0, |
| }; |
| |
| static const dcfg_arm_psci_driver_t psci_driver = { |
| .use_hvc = false, |
| .reboot_args = { 1, 0, 0 }, |
| .reboot_bootloader_args = { 4, 0, 0 }, |
| .reboot_recovery_args = { 2, 0, 0 }, |
| }; |
| |
| static const dcfg_arm_generic_timer_driver_t timer_driver = { |
| .irq_phys = 30, |
| }; |
| |
| static const dcfg_amlogic_rng_driver_t rng_driver = { |
| .rng_data_phys = (uint64_t)P_RNG_USR_DATA, |
| .rng_status_phys = (uint64_t)P_RNG_USR_STS, |
| .rng_refresh_interval_usec = HW_RNG_RESEEDING_INTERVAL_MICROS, |
| }; |
| |
| #define WDT_CTRL 0xffd0f0d0 |
| #define WDT_PET 0xffd0f0dc |
| |
| #define WATCHDOG_TIMEOUT_SECONDS 5 |
| #define SECONDS_TO_NANOSECONDS 1000000000LL |
| |
| static dcfg_generic_32bit_watchdog_t watchdog_driver = { |
| .pet_action = { |
| .addr = WDT_PET, |
| .clr_mask = 0xffffffff, |
| .set_mask = 0x00000000, |
| }, |
| .enable_action = { |
| .addr = WDT_CTRL, |
| .clr_mask = 0x00000000, |
| .set_mask = 0x00040000, |
| }, |
| .disable_action = { |
| .addr = WDT_CTRL, |
| .clr_mask = 0x00040000, |
| .set_mask = 0x00000000, |
| }, |
| .watchdog_period_nsec = |
| WATCHDOG_TIMEOUT_SECONDS * SECONDS_TO_NANOSECONDS, |
| .flags = KDRV_GENERIC_32BIT_WATCHDOG_FLAG_ENABLED, |
| }; |
| |
| /** |
| * disable_watchdog_petting() - disable petting of watchdog |
| * |
| * The function disables the petting function of watchdog driver by |
| * faking a pet_action.addr and setting the set_mask anc clr_mask |
| * field to be 0. |
| * |
| * It is mainly used for testing watchdog after booting into zircon. |
| * i.e., whether it triggers a reboot if zircon is not able to pet it in time |
| */ |
| void disable_watchdog_petting(void) |
| { |
| watchdog_driver.pet_action.addr = watchdog_driver.enable_action.addr; |
| watchdog_driver.pet_action.set_mask = 0; // no set |
| watchdog_driver.pet_action.clr_mask = 0; // no clear |
| } |
| |
| static const zbi_platform_id_t platform_id = { |
| .vid = PDEV_VID_GOOGLE, |
| .pid = PDEV_PID_SHERLOCK, |
| .board_name = "sherlock", |
| }; |
| |
| enum { |
| PART_TPL, |
| PART_FTS, |
| PART_FACTORY, |
| PART_ZIRCON_B, |
| PART_ZIRCON_A, |
| PART_ZIRCON_R, |
| PART_FVM, |
| PART_SYS_CONFIG, |
| PART_MIGRATION, |
| PART_COUNT, |
| }; |
| |
| int append_zbi_item_or_log(void *zbi, size_t capacity, uint32_t type, |
| uint32_t extra, const void *payload, size_t size) |
| { |
| if (size > 0xFFFFFFFFU) { |
| printf("Error: ZBI item 0x%08X/0x%X is too large (%zu bytes)\n", |
| type, extra, size); |
| return -1; |
| } |
| |
| zbi_result_t result = zbi_create_entry_with_payload( |
| zbi, capacity, type, extra, 0 /*flags*/, payload, size); |
| if (result != ZBI_RESULT_OK) { |
| printf("Error: Failed to add ZBI item 0x%08X/0x%X (code %d)\n", |
| type, extra, result); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static void *mandatory_memset(void *dst, int c, size_t n) |
| { |
| volatile unsigned char *out = dst; |
| size_t i = 0; |
| |
| for (i = 0; i < n; ++i) { |
| out[i] = (unsigned char)c; |
| } |
| return dst; |
| } |
| |
| #define FACTORY_MAC_ADDR_BUFF_LEN 30 |
| |
| /* fs_read() takes a ulong address to read into, make sure this is actually |
| big enough to hold any memory address. */ |
| _Static_assert(sizeof(ulong) >= sizeof(char *), |
| "fs_read() address type is too small"); |
| |
| static int add_mac_addresses(zbi_header_t *zbi, size_t capacity) |
| { |
| char buffer[FACTORY_MAC_ADDR_BUFF_LEN]; |
| u64 fullmac[2]; |
| u8 mac_addr[6]; |
| loff_t len_read; |
| int mac_num, i; |
| |
| if (fs_set_blk_dev(NEWMAN_FACTORY_IF, NEWMAN_FACTORY_PART, |
| FS_TYPE_EXT)) { |
| printf("set_blk_dev %s-%s failed.\n", NEWMAN_FACTORY_IF, |
| NEWMAN_FACTORY_PART); |
| return -1; |
| } |
| |
| if (fs_read(NEWMAN_FACTORY_MAC_ADDR_FILE, (ulong)buffer, 0, |
| FACTORY_MAC_ADDR_BUFF_LEN, &len_read)) { |
| printf("Failed to read Mac Addresses from Factory partition\n"); |
| return -1; |
| } |
| if (len_read != NEWMAN_FACTORY_MAC_ADDR_FILE_LEN) { |
| printf("Factory MAC Addr File length (%lld) incorrect.\n", |
| len_read); |
| return -1; |
| } |
| buffer[len_read] = '\0'; |
| |
| /* |
| * "buffer" should now contain two hex strings separated by \n, |
| * for a total of 25 bytes. Separate into 2 C strings and convert. |
| */ |
| buffer[len_read / 2] = '\0'; |
| fullmac[0] = simple_strtoull(buffer, NULL, 16); |
| fullmac[1] = simple_strtoull(&buffer[(len_read / 2) + 1], NULL, 16); |
| |
| for (mac_num = 0; mac_num < ARRAY_SIZE(fullmac); mac_num++) { |
| for (i = ARRAY_SIZE(mac_addr) - 1; i >= 0; i--) { |
| mac_addr[i] = (u8)(fullmac[mac_num] & 0xff); |
| fullmac[mac_num] >>= 8; |
| } |
| if (append_zbi_item_or_log(zbi, capacity, |
| ZBI_TYPE_DRV_MAC_ADDRESS, mac_num, |
| mac_addr, sizeof(mac_addr))) { |
| return -1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int add_serial_number(zbi_header_t *zbi, size_t capacity) |
| { |
| char *s; |
| if (!(s = env_get("serial#")) || (*s == '\0')) { |
| printf("%s: Failed to retrieve serial number.\n", __func__); |
| return -1; |
| } |
| return append_zbi_item_or_log(zbi, capacity, ZBI_TYPE_SERIAL_NUMBER, 0, |
| s, strlen(s)); |
| } |
| |
| static int add_board_info(zbi_header_t *zbi, size_t capacity) |
| { |
| zbi_board_info_t board_info = {}; |
| char *s; |
| if (((s = env_get("hw_id")) != NULL) && (*s != '\0')) { |
| uint32_t hw_id = simple_strtoul(s, NULL, 16); |
| board_info.revision = hw_id; |
| } else { |
| // Should we error out here? Existing behavior was to continue adding |
| // board_info with a default 0 "revision"; keep this behavior for now, |
| // but it might make more sense to just skip this item instead if we |
| // can determine nothing in the OS depends on it being present. |
| printf("Failed to retrieve Board Revision\n"); |
| } |
| |
| return append_zbi_item_or_log(zbi, capacity, ZBI_TYPE_DRV_BOARD_INFO, 0, |
| &board_info, sizeof(board_info)); |
| } |
| |
| // Define a cpu topology for the system that captures how all of the cores are |
| // connected and grouped. Since this is a GICv2 system we include the ID for the |
| // cores, especially since it is a tricky one with a gap between the little and |
| // big clusters. |
| static int add_cpu_topology(zbi_header_t *zbi, size_t capacity) |
| { |
| int index = 0; |
| int logical_processor = 0; |
| int big_cluster = 0, little_cluster = 0; |
| |
| zbi_topology_node_t nodes[8]; |
| |
| little_cluster = index++; |
| nodes[little_cluster] = |
| (zbi_topology_node_t){ .entity_type = |
| ZBI_TOPOLOGY_ENTITY_CLUSTER, |
| .parent_index = ZBI_TOPOLOGY_NO_PARENT, |
| .entity = { |
| .cluster = { |
| .performance_class = |
| 0, // low performance cluster |
| } } }; |
| |
| nodes[index++] = |
| (zbi_topology_node_t){ .entity_type = |
| ZBI_TOPOLOGY_ENTITY_PROCESSOR, |
| .parent_index = little_cluster, |
| .entity = { |
| .processor = { |
| .logical_ids = { logical_processor++ }, |
| .logical_id_count = 1, |
| .flags = |
| ZBI_TOPOLOGY_PROCESSOR_PRIMARY, |
| .architecture = |
| ZBI_TOPOLOGY_ARCH_ARM, |
| .architecture_info = { |
| .arm = { |
| .cluster_1_id = |
| 0, |
| .cpu_id = |
| 0, |
| .gic_id = |
| 0, |
| } } } } }; |
| nodes[index++] = |
| (zbi_topology_node_t){ .entity_type = |
| ZBI_TOPOLOGY_ENTITY_PROCESSOR, |
| .parent_index = little_cluster, |
| .entity = { |
| .processor = { |
| .logical_ids = { logical_processor++ }, |
| .logical_id_count = 1, |
| .flags = 0, |
| .architecture = |
| ZBI_TOPOLOGY_ARCH_ARM, |
| .architecture_info = { |
| .arm = { |
| .cluster_1_id = |
| 0, |
| .cpu_id = |
| 1, |
| .gic_id = |
| 1, |
| } } } } }; |
| |
| big_cluster = index++; |
| nodes[big_cluster] = |
| (zbi_topology_node_t){ .entity_type = |
| ZBI_TOPOLOGY_ENTITY_CLUSTER, |
| .parent_index = ZBI_TOPOLOGY_NO_PARENT, |
| .entity = { |
| .cluster = { |
| .performance_class = |
| 1, // high performance cluster |
| } } }; |
| |
| nodes[index++] = |
| (zbi_topology_node_t){ .entity_type = |
| ZBI_TOPOLOGY_ENTITY_PROCESSOR, |
| .parent_index = big_cluster, |
| .entity = { |
| .processor = { |
| .logical_ids = { logical_processor++ }, |
| .logical_id_count = 1, |
| .flags = 0, |
| .architecture = |
| ZBI_TOPOLOGY_ARCH_ARM, |
| .architecture_info = { |
| .arm = { |
| .cluster_1_id = |
| 1, |
| .cpu_id = |
| 0, |
| .gic_id = |
| 4, |
| } } } } }; |
| nodes[index++] = |
| (zbi_topology_node_t){ .entity_type = |
| ZBI_TOPOLOGY_ENTITY_PROCESSOR, |
| .parent_index = big_cluster, |
| .entity = { |
| .processor = { |
| .logical_ids = { logical_processor++ }, |
| .logical_id_count = 1, |
| .flags = 0, |
| .architecture = |
| ZBI_TOPOLOGY_ARCH_ARM, |
| .architecture_info = { |
| .arm = { |
| .cluster_1_id = |
| 1, |
| .cpu_id = |
| 1, |
| .gic_id = |
| 5, |
| } } } } }; |
| nodes[index++] = |
| (zbi_topology_node_t){ .entity_type = |
| ZBI_TOPOLOGY_ENTITY_PROCESSOR, |
| .parent_index = big_cluster, |
| .entity = { |
| .processor = { |
| .logical_ids = { logical_processor++ }, |
| .logical_id_count = 1, |
| .flags = 0, |
| .architecture = |
| ZBI_TOPOLOGY_ARCH_ARM, |
| .architecture_info = { |
| .arm = { |
| .cluster_1_id = |
| 1, |
| .cpu_id = |
| 2, |
| .gic_id = |
| 6, |
| } } } } }; |
| nodes[index++] = |
| (zbi_topology_node_t){ .entity_type = |
| ZBI_TOPOLOGY_ENTITY_PROCESSOR, |
| .parent_index = big_cluster, |
| .entity = { |
| .processor = { |
| .logical_ids = { logical_processor++ }, |
| .logical_id_count = 1, |
| .flags = 0, |
| .architecture = |
| ZBI_TOPOLOGY_ARCH_ARM, |
| .architecture_info = { |
| .arm = { |
| .cluster_1_id = |
| 1, |
| .cpu_id = |
| 3, |
| .gic_id = |
| 7, |
| } } } } }; |
| |
| return append_zbi_item_or_log(zbi, capacity, ZBI_TYPE_CPU_TOPOLOGY, |
| sizeof(zbi_topology_node_t), &nodes, |
| sizeof(zbi_topology_node_t) * index); |
| } |
| |
| static int add_reboot_reason(zbi_header_t *zbi, size_t capacity) |
| { |
| // See cmd/amlogic/cmd_reboot.c |
| const uint32_t reboot_mode_val = ((readl(AO_SEC_SD_CFG15) >> 12) & 0xf); |
| |
| zbi_hw_reboot_reason_t reboot_reason; |
| switch (reboot_mode_val) { |
| case AMLOGIC_COLD_BOOT: |
| reboot_reason = ZBI_HW_REBOOT_COLD; |
| break; |
| case AMLOGIC_NORMAL_BOOT: |
| case AMLOGIC_FACTORY_RESET_REBOOT: |
| case AMLOGIC_UPDATE_REBOOT: |
| case AMLOGIC_FASTBOOT_REBOOT: |
| case AMLOGIC_SUSPEND_REBOOT: |
| case AMLOGIC_HIBERNATE_REBOOT: |
| case AMLOGIC_BOOTLOADER_REBOOT: |
| case AMLOGIC_SHUTDOWN_REBOOT: |
| case AMLOGIC_RPMBP_REBOOT: |
| case AMLOGIC_QUIESCENT_REBOOT: |
| case AMLOGIC_CRASH_REBOOT: |
| case AMLOGIC_KERNEL_PANIC: |
| case AMLOGIC_RECOVERY_QUIESCENT_REBOOT: |
| reboot_reason = ZBI_HW_REBOOT_WARM; |
| break; |
| case AMLOGIC_WATCHDOG_REBOOT: |
| reboot_reason = ZBI_HW_REBOOT_WATCHDOG; |
| break; |
| default: |
| reboot_reason = ZBI_HW_REBOOT_UNDEFINED; |
| break; |
| } |
| |
| return append_zbi_item_or_log(zbi, capacity, ZBI_TYPE_HW_REBOOT_REASON, |
| 0, &reboot_reason, sizeof(reboot_reason)); |
| } |
| |
| #define WATCHDOG_DEV_NAME "watchdog" |
| static void start_meson_watchdog(void) |
| { |
| struct udevice *wdt_dev; |
| int ret = uclass_get_device_by_name(UCLASS_WDT, WATCHDOG_DEV_NAME, |
| &wdt_dev); |
| if (ret < 0) { |
| printf("failed to get meson watchdog\n"); |
| return; |
| } |
| |
| printf("starting meson watchdog\n"); |
| // Note: the watchdog uclass expects a timeout in milliseconds, but the |
| // Amlogic code uses seconds instead. |
| wdt_start(wdt_dev, WATCHDOG_TIMEOUT_SECONDS, 0); |
| wdt_reset(wdt_dev); |
| } |
| |
| // fills an 8 char buffer with the lowercase hex representation of the given |
| // value. |
| // WARNING this does not add a '\0' to the end of the buffer. |
| static inline void uint32_to_hex(uint32_t val, char buf[static 8]) |
| { |
| static const char hex[] = "0123456789abcdef"; |
| int i = 0; |
| |
| for (i = 7; i >= 0; i--) { |
| buf[i] = hex[val & 0xF]; |
| val >>= 4; |
| } |
| } |
| |
| // Reads a value from the userspace hardware random number generator. |
| // |
| // This assumes that the drng has been previously seeded. Callers should |
| // poll P_RNG_USR_STS beforehand to make sure that a reseed occurred. |
| static inline uint32_t read_hw_rng(void) |
| { |
| return readl(P_RNG_USR_DATA); |
| } |
| |
| // Gathers CMDLINE_ENTROPY_BITS bits of entropy from the hardware random |
| // number generator and appends it as a ZBI_TYPE_CMDLINE for zircon to seed |
| // its cprng. |
| // |
| // This function will sleep a maximum of HW_RNG_RESEEDING_INTERVAL_MICROS * |
| // (CMDLINE_ENTROPY_BITS / 32) + ENTROPY_COLLECTION_DEADLINE_MICROS. |
| // If the function can't gather enough entropy after after exceeding |
| // the deadline, it will return -1 and the cmdline zbi will not be added. |
| static int add_cmdline_entropy(zbi_header_t *zbi, size_t capacity) |
| { |
| strcpy(entropy_cmdline, zircon_entropy_arg); |
| char *entropy = entropy_cmdline + strlen(zircon_entropy_arg); |
| uint32_t elapsed_time_us = 0; |
| int i = 0; |
| |
| for (i = 0; i < CMDLINE_ENTROPY_BITS; i += 32) { |
| // Reading a 1 in the RNG_USR_STS means that the |
| // hw rng has been reseeded. Wait until we see a 1, |
| // without exceeding the global deadline. |
| while ((readl(P_RNG_USR_STS) & USR_RAND32_RDY) != 1) { |
| udelay(1); |
| elapsed_time_us++; |
| if (elapsed_time_us > |
| ENTROPY_COLLECTION_DEADLINE_MICROS) { |
| return -1; |
| } |
| } |
| |
| uint32_to_hex(read_hw_rng(), entropy); |
| entropy += 8; |
| |
| // According to the docs, this should guarantee a reseed. |
| udelay(HW_RNG_RESEEDING_INTERVAL_MICROS); |
| } |
| *entropy = '\0'; |
| |
| int ret = append_zbi_item_or_log(zbi, capacity, ZBI_TYPE_CMDLINE, 0, |
| entropy_cmdline, |
| sizeof(entropy_cmdline)); |
| |
| mandatory_memset(entropy_cmdline, '\0', sizeof(entropy_cmdline)); |
| return ret; |
| } |
| |
| // Buffer to keep staged ZBI files. |
| // We store these in their own ZBI container, which takes up a little extra |
| // space due to ZBI headers, but makes copying them over to the actual ZBI |
| // trivial. |
| // |
| // The test team needs to be able to put up to 3 SSH keys on a single device |
| // so make sure it's at least big enough for that. |
| static uint8_t zbi_files[4096] __attribute__((aligned(ZBI_ALIGNMENT))); |
| static bool zbi_files_initialized = false; |
| |
| int zircon_stage_zbi_file(const char *name, const uint8_t *data, |
| size_t data_len) |
| { |
| size_t name_len = strlen(name); |
| if (name_len > U8_MAX) { |
| printf("ZBI filename too long"); |
| return -1; |
| } |
| |
| // Payload = (name_length_byte + name + data), size must fit in a uint32_t. |
| size_t payload_length = 1 + name_len + data_len; |
| if (payload_length > U32_MAX || payload_length < data_len) { |
| printf("ZBI file data too large"); |
| return -1; |
| } |
| |
| if (!zbi_files_initialized) { |
| zbi_result_t result = zbi_init(zbi_files, sizeof(zbi_files)); |
| if (result != ZBI_RESULT_OK) { |
| printf("Failed to initialize zbi_files: %d\n", result); |
| return -1; |
| } |
| zbi_files_initialized = true; |
| } |
| |
| void *payload_as_void = NULL; |
| zbi_result_t result = |
| zbi_create_entry(zbi_files, sizeof(zbi_files), |
| ZBI_TYPE_BOOTLOADER_FILE, 0, 0, payload_length, |
| &payload_as_void); |
| if (result != ZBI_RESULT_OK) { |
| printf("Failed to create ZBI file entry: %d\n", result); |
| return -1; |
| } |
| |
| uint8_t *payload = payload_as_void; |
| payload[0] = name_len; |
| memcpy(&payload[1], name, name_len); |
| memcpy(&payload[1 + name_len], data, data_len); |
| |
| return 0; |
| } |
| |
| static int add_staged_zbi_files(zbi_header_t *zbi, size_t capacity) |
| { |
| if (!zbi_files_initialized) { |
| return 0; |
| } |
| |
| zbi_result_t result = zbi_extend(zbi, capacity, zbi_files); |
| if (result != ZBI_RESULT_OK) { |
| printf("Failed to add staged ZBI files: %d\n", result); |
| return -1; |
| } |
| |
| printf("Added staged ZBI files with total ZBI size %u\n", |
| ((zbi_header_t *)zbi_files)->length); |
| return 0; |
| } |
| |
| static int add_current_slot(zbi_header_t *zbi, size_t capacity, |
| AbrSlotIndex slot) |
| { |
| const char *suffix = AbrGetSlotSuffix(slot); |
| if (suffix[0] == '\0') { |
| printf("Invalid AbrSlotIndex: %d\n", slot); |
| return -1; |
| } |
| |
| char buffer[32]; |
| int length = |
| snprintf(buffer, sizeof(buffer), "zvb.current_slot=%s", suffix); |
| if (length >= sizeof(buffer)) { |
| printf("current_slot string too large: '%s'\n", buffer); |
| return -1; |
| } |
| |
| return append_zbi_item_or_log(zbi, capacity, ZBI_TYPE_CMDLINE, 0, |
| buffer, length + 1); |
| } |
| |
| // The list contains all files on factory filesystem |
| static const char *const factory_file_list[] = { |
| "client.crt", |
| "client.key", |
| "hw.txt", |
| "locale_list.txt", |
| "nf.key", |
| "pr3.crt", |
| "pr3.key", |
| "rgb_b_leda_cal.txt", |
| "rgb_b_ledg_cal.txt", |
| "rgb_b_off_cal.txt", |
| "rgb_b_on_cal.txt", |
| "rgb_c_leda_cal.txt", |
| "rgb_c_ledg_cal.txt", |
| "rgb_c_off_cal.txt", |
| "rgb_c_on_cal.txt", |
| "rgb_g_leda_cal.txt", |
| "rgb_g_ledg_cal.txt", |
| "rgb_g_off_cal.txt", |
| "rgb_g_on_cal.txt", |
| "rgb_r_leda_cal.txt", |
| "rgb_r_ledg_cal.txt", |
| "rgb_r_off_cal.txt", |
| "rgb_r_on_cal.txt", |
| "serial.txt", |
| "sounds/error.opus", |
| "sounds/err_no_lang_pack.opus", |
| "sounds/fdr.opus", |
| "sounds/mic_muted_warning.opus", |
| "sounds/mute.opus", |
| "sounds/unmute.opus", |
| "sounds/welcome.opus", |
| "weave.crt", |
| "weave.key", |
| "weave_device_id", |
| "weave_pairing_code", |
| "wv.key", |
| }; |
| |
| /* Adds a single bootfs factory file to the ZBI payload. |
| * |
| * @filename: ext4 filename. |
| * @data_off: offset from the payload start to this file's data. |
| * @max_bootfs_size: maximum payload size. |
| * @direntry_start: pointer to this file's zbi_bootfs_dirent_t in the payload |
| * buffer. Will be advanced past this entry on success, |
| * including padding. |
| * @payload_start: pointer to this file's data in the payload buffer. Will be |
| * advanced past this data on success, including padding. |
| * @data_len: will be filled with the file's data length. |
| * |
| * Returns 0 on success, nonzero on failure. |
| */ |
| static int add_factory_file(const char *filename, uint32_t data_off, |
| uint32_t max_bootfs_size, uint8_t **direntry_start, |
| uint8_t **payload_start, uint32_t *data_len) |
| { |
| const uint32_t name_len = strlen(filename) + 1; |
| const uint32_t max_data_size = |
| (max_bootfs_size - data_off) & ~(ZBI_BOOTFS_PAGE_SIZE - 1); |
| |
| if (fs_set_blk_dev(NEWMAN_FACTORY_IF, NEWMAN_FACTORY_PART, |
| FS_TYPE_EXT)) { |
| printf("set_blk_dev %s-%s failed.\n", NEWMAN_FACTORY_IF, |
| NEWMAN_FACTORY_PART); |
| return -1; |
| } |
| |
| loff_t len_read; |
| if (fs_read(filename, (ulong)*payload_start, 0, max_data_size, |
| &len_read)) { |
| printf("Failed to read file %s from Factory partition\n", |
| filename); |
| return -1; |
| } |
| |
| if (len_read > UINT_MAX) { |
| printf("File size overloads UINT32_MAX\n"); |
| return -1; |
| } |
| |
| *data_len = (uint32_t)len_read; |
| *payload_start += *data_len; |
| |
| uint32_t data_pad_size = ZBI_BOOTFS_PAGE_ALIGN(*data_len) - *data_len; |
| memset(*payload_start, 0, data_pad_size); |
| *payload_start += data_pad_size; |
| |
| // The caller has already reserved space for the dirent structures and |
| // filenames in the buffer, so we don't need to check for overflow here. |
| zbi_bootfs_dirent_t *entry_hdr = |
| (zbi_bootfs_dirent_t *)(*direntry_start); |
| entry_hdr->name_len = name_len; |
| entry_hdr->data_len = *data_len; |
| entry_hdr->data_off = data_off; |
| |
| *direntry_start += offsetof(zbi_bootfs_dirent_t, name); |
| memcpy(*direntry_start, filename, name_len); |
| *direntry_start += name_len; |
| |
| uint32_t full_dirent_size = ZBI_BOOTFS_DIRENT_SIZE(name_len); |
| uint32_t after_name_offset = |
| offsetof(zbi_bootfs_dirent_t, name[name_len]); |
| uint32_t dirent_pad_size = full_dirent_size - after_name_offset; |
| memset(*direntry_start, 0, dirent_pad_size); |
| *direntry_start += dirent_pad_size; |
| |
| return 0; |
| } |
| |
| /* Loads factory files into a BOOTFS ZBI payload. |
| * |
| * @bootfs_header: start of the payload buffer to fill. |
| * @max_bootfs_size: maximum payload size. |
| * @file_count: number of files in the ZBI item. |
| * @dirsize: size in bytes of the zbi_bootfs_dirent_t array. |
| * @bootfs_size: set to the actual payload size. |
| * |
| * Returns 0 on success, nonzero on failure. |
| */ |
| static int add_bootfs_factory_files(zbi_bootfs_header_t *bootfs_header, |
| uint32_t max_bootfs_size, |
| uint32_t file_count, uint32_t dirsize, |
| uint32_t *bootfs_size) |
| { |
| if (sizeof(*bootfs_header) > max_bootfs_size) { |
| printf("ERROR: can't fit bootfs header in ZBI payload (%zu > %u)\n", |
| sizeof(*bootfs_header), max_bootfs_size); |
| return 1; |
| } |
| bootfs_header->magic = ZBI_BOOTFS_MAGIC; |
| bootfs_header->dirsize = dirsize; |
| bootfs_header->reserved0 = 0; |
| bootfs_header->reserved1 = 0; |
| |
| uint32_t dir_entries_end_offset = dirsize + sizeof(zbi_bootfs_header_t); |
| uint32_t data_off = ZBI_BOOTFS_PAGE_ALIGN(dir_entries_end_offset); |
| if (data_off < dirsize) { |
| printf("ERROR: bootfs dirsize overflow (dirsize = %u, data_off = %u)\n", |
| dirsize, data_off); |
| return 1; |
| } |
| if (data_off > max_bootfs_size) { |
| printf("ERROR: can't fit bootfs dir entries in ZBI payload (%u > %u)\n", |
| data_off, max_bootfs_size); |
| return 1; |
| } |
| |
| uint8_t *header_start = (uint8_t *)bootfs_header; |
| uint8_t *entry_ptr = header_start + sizeof(zbi_bootfs_header_t); |
| uint8_t *payload_padding_start = header_start + dir_entries_end_offset; |
| uint8_t *payload_start = header_start + data_off; |
| |
| memset(payload_padding_start, 0, |
| (size_t)(payload_start - payload_padding_start)); |
| |
| uint32_t i; |
| for (i = 0; i < file_count; i++) { |
| const char *const filename = factory_file_list[i]; |
| uint32_t data_len = 0; |
| |
| payload_padding_start = payload_start; |
| int ret = |
| add_factory_file(filename, data_off, max_bootfs_size, |
| &entry_ptr, &payload_start, &data_len); |
| if (ret != 0) { |
| // Log the error, but try to keep going and add the rest of the |
| // factory files. |
| printf("ERROR: failed to add factory file %s\n", |
| filename); |
| } |
| payload_padding_start += data_len; |
| |
| data_off += ZBI_BOOTFS_PAGE_ALIGN(data_len); |
| memset(payload_padding_start, 0, |
| (size_t)(payload_start - payload_padding_start)); |
| } |
| |
| *bootfs_size = data_off; |
| return 0; |
| } |
| |
| /* Adds a ZBI item containing the factory files to the given container. |
| * |
| * On Newman, factory files are stored in a ext4 partition. It has been observed |
| * that some devices have ext4 64 bit flag set, which causes fuchsia to refuse |
| * to process. Therefore, we instead pass all files as ZBI items to fuchsia |
| * directly from the bootloader. |
| * |
| * @zbi: ZBI container to add the factory files to. |
| * @capacity: ZBI container max capacity. |
| * |
| * Returns 0 on success, nonzero on failure. |
| */ |
| static int add_factory_data(zbi_header_t *zbi, size_t capacity) |
| { |
| // Add an empty ZBI header for factory data. Factory data is formated as BOOTFS. |
| // BOOTFS is a trivial "filesystem" format. |
| // |
| // It consists of a zbi_bootfs_header_t followed by a series of zbi_bootfs_dirent_t structs. |
| // After the zbi_bootfs_dirent_t structs, file data is placed. |
| // File data offsets are page aligned (multiple of 4096). |
| // zbi_bootfs_dirent_t structs start on uint32 boundaries. |
| void *payload = NULL; |
| uint32_t max_payload_size = 0; |
| zbi_result_t result = zbi_get_next_entry_payload( |
| zbi, capacity, &payload, &max_payload_size); |
| if (result != ZBI_RESULT_OK) { |
| printf("ERROR: zbi_get_next_entry_payload() failed: %d\n", |
| result); |
| return 1; |
| } |
| |
| // Determine how much space is needed to store all of the directory entries. |
| uint32_t dirsize = 0; |
| uint32_t file_count = (uint32_t)(sizeof(factory_file_list) / |
| sizeof(*factory_file_list)); |
| |
| uint32_t i; |
| for (i = 0; i < file_count; i++) { |
| const char *const filename = factory_file_list[i]; |
| dirsize += ZBI_BOOTFS_DIRENT_SIZE(strlen(filename) + 1); |
| } |
| |
| // Mark the start of the zbi_bootfs_header_t. |
| // This header is bootfs-specific and stores data related to the number of |
| // directory entries. |
| uint32_t bootfs_size = 0; |
| int ret = add_bootfs_factory_files((zbi_bootfs_header_t *)payload, |
| max_payload_size, file_count, |
| dirsize, &bootfs_size); |
| if (ret != 0) { |
| printf("ERROR: add_bootfs_factory_files() failed\n"); |
| return ret; |
| } |
| |
| // Finally, add the ZBI item using the newly created payload. |
| result = |
| zbi_create_entry(zbi, capacity, ZBI_TYPE_STORAGE_BOOTFS_FACTORY, |
| 0, 0, bootfs_size, NULL); |
| if (result != ZBI_RESULT_OK) { |
| printf("ERROR: zbi_create_entry() failed: %d\n", result); |
| return 1; |
| } |
| return 0; |
| } |
| |
| /* We do this a lot in zircon_fixup_zbi(), a macro helps with boilerplate. |
| * Generally it's best to fail loudly if we can't add a ZBI item; we want to |
| * know immediately if ZBI items are missing, not later when random things |
| * stop working in the OS. */ |
| #define RETURN_IF_NONZERO(val) \ |
| do { \ |
| int ret = (val); \ |
| if (ret != 0) { \ |
| return ret; \ |
| } \ |
| } while (0) |
| |
| |
| int zircon_fixup_zbi_no_slot(zbi_header_t *zbi, size_t capacity) { |
| /* |
| * allocate crashlog save area before 0x5f800000-0x60000000 |
| * reserved area |
| */ |
| zbi_nvram_t nvram; |
| |
| nvram.base = 0x5f800000 - NVRAM_LENGTH; |
| nvram.length = NVRAM_LENGTH; |
| RETURN_IF_NONZERO(append_zbi_item_or_log(zbi, capacity, ZBI_TYPE_NVRAM, |
| 0, &nvram, sizeof(nvram))); |
| |
| /* add memory configuration */ |
| RETURN_IF_NONZERO( |
| append_zbi_item_or_log(zbi, capacity, ZBI_TYPE_MEM_CONFIG, 0, |
| &mem_config, sizeof(mem_config))); |
| |
| /* add kernel drivers */ |
| RETURN_IF_NONZERO(append_zbi_item_or_log( |
| zbi, capacity, ZBI_TYPE_KERNEL_DRIVER, KDRV_AMLOGIC_UART, |
| &uart_driver, sizeof(uart_driver))); |
| RETURN_IF_NONZERO(append_zbi_item_or_log( |
| zbi, capacity, ZBI_TYPE_KERNEL_DRIVER, KDRV_ARM_GIC_V2, |
| &gicv2_driver, sizeof(gicv2_driver))); |
| RETURN_IF_NONZERO(append_zbi_item_or_log( |
| zbi, capacity, ZBI_TYPE_KERNEL_DRIVER, KDRV_ARM_PSCI, |
| &psci_driver, sizeof(psci_driver))); |
| RETURN_IF_NONZERO(append_zbi_item_or_log( |
| zbi, capacity, ZBI_TYPE_KERNEL_DRIVER, KDRV_ARM_GENERIC_TIMER, |
| &timer_driver, sizeof(timer_driver))); |
| RETURN_IF_NONZERO(append_zbi_item_or_log( |
| zbi, capacity, ZBI_TYPE_KERNEL_DRIVER, |
| KDRV_GENERIC_32BIT_WATCHDOG, &watchdog_driver, |
| sizeof(watchdog_driver))); |
| RETURN_IF_NONZERO(append_zbi_item_or_log( |
| zbi, capacity, ZBI_TYPE_KERNEL_DRIVER, KDRV_AMLOGIC_RNG, |
| &rng_driver, sizeof(rng_driver))); |
| |
| char uboot_ver[] = "bootloader.name=" U_BOOT_VERSION_STRING; |
| // Zircon's cmdline parameters cannot contain spaces so |
| // convert spaces in autogenerated U-boot version string |
| // to underscores. |
| // See zircon/docs/kernel_cmdline.md |
| int i; |
| int len = strlen(uboot_ver); |
| for (i = 0; i < len; i++) { |
| if (uboot_ver[i] == ' ') { |
| uboot_ver[i] = '_'; |
| } |
| } |
| |
| RETURN_IF_NONZERO(append_zbi_item_or_log( |
| zbi, capacity, ZBI_TYPE_CMDLINE, 0, uboot_ver, len + 1)); |
| |
| if (add_serial_number(zbi, capacity) != 0) { |
| // TODO(b/171923279): actually error out here once we can handle |
| // empty factory partitions. |
| printf("ERROR: failed to add serial number\n"); |
| } |
| |
| /* Add board-specific information */ |
| RETURN_IF_NONZERO(add_board_info(zbi, capacity)); |
| |
| /* add platform ID */ |
| RETURN_IF_NONZERO( |
| append_zbi_item_or_log(zbi, capacity, ZBI_TYPE_PLATFORM_ID, 0, |
| &platform_id, sizeof(platform_id))); |
| |
| RETURN_IF_NONZERO(add_cpu_topology(zbi, capacity)); |
| |
| RETURN_IF_NONZERO(add_cmdline_entropy(zbi, capacity)); |
| |
| RETURN_IF_NONZERO(add_factory_data(zbi, capacity)); |
| |
| if (add_mac_addresses(zbi, capacity) != 0) { |
| // TODO(b/171923279): actually error out here once we can handle |
| // empty factory partitions. |
| printf("ERROR: failed to add MAC address\n"); |
| } |
| |
| // Only append extra cmdline args if unlocked |
| bool unlocked; |
| if (zircon_vboot_is_unlocked(&unlocked)) { |
| fprintf(stderr, "Error: unable to get unlock status\n"); |
| } else if (unlocked) { |
| if (zircon_append_cmdline(zbi, capacity)) { |
| fprintf(stderr, "ERROR: unable to append boot_args.\n"); |
| return -1; |
| } |
| } |
| |
| RETURN_IF_NONZERO(add_reboot_reason(zbi, capacity)); |
| |
| if (watchdog_driver.flags & KDRV_GENERIC_32BIT_WATCHDOG_FLAG_ENABLED) { |
| start_meson_watchdog(); |
| } |
| |
| RETURN_IF_NONZERO(add_staged_zbi_files(zbi, capacity)); |
| |
| return 0; |
| } |
| |
| int zircon_fixup_zbi(zbi_header_t *zbi, size_t capacity, AbrSlotIndex slot) |
| { |
| RETURN_IF_NONZERO(add_current_slot(zbi, capacity, slot)); |
| return zircon_fixup_zbi_no_slot(zbi, capacity); |
| } |