blob: 6cd61b1ebd1a8d29024581df1586daecbe35d839 [file] [log] [blame]
/*
* 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);
}