blob: aa43361a4580c736d22ed3a3a3d752e6b4b391af [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 <emmc_storage.h>
#include <fs.h>
#include <mmc.h>
#include <u-boot/sha256.h>
#include <version.h>
#include <wdt.h>
#include <zircon_uboot/boot_args.h>
#include <zircon_uboot/zircon.h>
#ifndef CONFIG_FACTORY_BOOT_KVS
#error "Factory_boot KVS has to be enabled for this board!!!"
#endif
#include <factory_boot_kvs.h>
#include <tee/ta_vx_helper.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_LUIS 12
#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_LUIS,
.board_name = "luis",
};
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,
};
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;
}
static int default_mac_address(int n, u64* fullmac)
{
// add default MAC addresses like we make default serial numbers,
// from a hash of the eMMC CID
static u64 default_mac;
if (!default_mac) {
struct mmc* mmc = find_mmc_device(STORAGE_DEV_EMMC);
if (!mmc) {
printf("Cannot find eMMC dev.\n");
return -1;
}
u8 hash[SHA256_DIGEST_SIZE];
sha256_csum_wd((void *)mmc->cid, sizeof mmc->cid, hash, CHUNKSZ_SHA256);
memcpy(&default_mac, hash, 5);
default_mac |= 0x02ull << 40;
}
if (!fullmac) {
return -2;
}
*fullmac = default_mac;
return 0;
}
#define MAC_ADDR_KEY_1 "mac_addr1"
#define MAC_ADDR_KEY_2 "mac_addr2"
// 0 = WiFi MAC, 1 = BT/enet MAC.
#define NUM_MAC_ADDRESSES 2
static int add_mac_addresses(zbi_header_t *zbi)
{
const char *mac_addr_keys[] = { MAC_ADDR_KEY_1, MAC_ADDR_KEY_2 };
u8 mac_addr[6];
int mac_num, i;
for (mac_num = 0; mac_num < NUM_MAC_ADDRESSES; mac_num++) {
u64 fullmac;
if (FbKvsGetULong(mac_addr_keys[mac_num], &fullmac) !=
kFbKvsResultOk) {
printf("%s: ERROR: FbKvsGetULong() failed to read MAC ADDR %d\n",
__func__, mac_num);
if (default_mac_address(mac_num, &fullmac) != 0)
continue;
}
for (i = ARRAY_SIZE(mac_addr) - 1; i >= 0; i--) {
mac_addr[i] = (u8)(fullmac & 0xff);
fullmac >>= 8;
}
zircon_append_boot_item(zbi, ZBI_TYPE_DRV_MAC_ADDRESS, mac_num,
mac_addr, sizeof(mac_addr));
}
return 0;
}
static void add_serial_number(zbi_header_t* zbi) {
char* s;
if (!(s = env_get("serial#")) || (*s == '\0')) {
printf("%s: Failed to retrieve serial number.\n", __func__);
return;
}
zircon_append_boot_item(zbi, ZBI_TYPE_SERIAL_NUMBER, 0, s, strlen(s));
}
static void add_board_info(zbi_header_t* zbi)
{
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 {
printf("Failed to retrieve Board Revision\n");
}
zircon_append_boot_item(zbi, 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 void add_cpu_topology(zbi_header_t* zbi)
{
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,
}
}
}
}
};
zircon_append_boot_item(zbi, ZBI_TYPE_CPU_TOPOLOGY, sizeof(zbi_topology_node_t),
&nodes, sizeof(zbi_topology_node_t) * index);
}
static void add_reboot_reason(zbi_header_t* zbi) {
// 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;
}
zircon_append_boot_item(zbi, 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) {
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';
zircon_append_boot_item(zbi, ZBI_TYPE_CMDLINE, 0, entropy_cmdline,
sizeof(entropy_cmdline));
mandatory_memset(entropy_cmdline, '\0', sizeof(entropy_cmdline));
return 0;
}
#define FB_KVS_SEAL_NAME "factory_verity_seal"
#define FB_KVS_SEAL_SIZE 32
#define KERNEL_ARG_SEAL_NAME "factory_verity_seal="
static int add_factory_verity_seal(zbi_header_t *zbi) {
uint8_t seal_data[FB_KVS_SEAL_SIZE] = {0};
char seal_hex_str[sizeof(KERNEL_ARG_SEAL_NAME) + FB_KVS_SEAL_SIZE * 2];
size_t off = strlen(KERNEL_ARG_SEAL_NAME);
static const char hex[] = "0123456789abcdef";
size_t size = sizeof(seal_data);
size_t i;
if (FbKvsGetData(FB_KVS_SEAL_NAME, seal_data, &size) != kFbKvsResultOk) {
printf("%s: ERROR: FbKvsGetData() failed to read '"
FB_KVS_SEAL_NAME"' value\n", __func__);
return -1;
}
memcpy(seal_hex_str, KERNEL_ARG_SEAL_NAME, strlen(KERNEL_ARG_SEAL_NAME));
/* convert to hex string */
for (i = 0; i < size; ++i) {
seal_hex_str[off + 2 * i] = hex[seal_data[i] >> 4];
seal_hex_str[off + 2 * i + 1] = hex[seal_data[i] & 0xf];
}
seal_hex_str[off + i*2] = '\0';
zircon_append_boot_item(zbi, ZBI_TYPE_CMDLINE, 0, seal_hex_str,
sizeof(seal_hex_str));
return 0;
}
int zircon_preboot(zbi_header_t *zbi)
{
/*
* allocate crashlog save area before 0x5f800000-0x60000000
* reserved area
*/
zbi_nvram_t nvram;
nvram.base = 0x5f800000 - NVRAM_LENGTH;
nvram.length = NVRAM_LENGTH;
zircon_append_boot_item(zbi, ZBI_TYPE_NVRAM, 0, &nvram, sizeof(nvram));
/* add memory configuration */
zircon_append_boot_item(zbi, ZBI_TYPE_MEM_CONFIG, 0, &mem_config,
sizeof(mem_config));
/* add kernel drivers */
zircon_append_boot_item(zbi, ZBI_TYPE_KERNEL_DRIVER, KDRV_AMLOGIC_UART,
&uart_driver, sizeof(uart_driver));
zircon_append_boot_item(zbi, ZBI_TYPE_KERNEL_DRIVER, KDRV_ARM_GIC_V2,
&gicv2_driver, sizeof(gicv2_driver));
zircon_append_boot_item(zbi, ZBI_TYPE_KERNEL_DRIVER, KDRV_ARM_PSCI,
&psci_driver, sizeof(psci_driver));
zircon_append_boot_item(zbi, ZBI_TYPE_KERNEL_DRIVER,
KDRV_ARM_GENERIC_TIMER,
&timer_driver, sizeof(timer_driver));
zircon_append_boot_item(zbi, ZBI_TYPE_KERNEL_DRIVER,
KDRV_GENERIC_32BIT_WATCHDOG,
&watchdog_driver, sizeof(watchdog_driver));
zircon_append_boot_item(zbi, 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] = '_';
}
}
zircon_append_boot_item(zbi, ZBI_TYPE_CMDLINE, 0, uboot_ver, len + 1);
add_serial_number(zbi);
/* Add board-specific information */
add_board_info(zbi);
/* add platform ID */
zircon_append_boot_item(zbi, ZBI_TYPE_PLATFORM_ID, 0, &platform_id,
sizeof(platform_id));
add_cpu_topology(zbi);
int ret = add_cmdline_entropy(zbi);
if (ret < 0) {
panic("ERROR: unable to gather enough entropy to pass via cmdline!\n");
}
ret = add_mac_addresses(zbi);
if (ret < 0) {
printf("ERROR: unable to read MAC addresses from the"
" factory_boot partition!\n");
}
ret = add_factory_verity_seal(zbi);
if (ret < 0) {
printf("ERROR: unable to read Factory verity seal value from the"
" factory_boot partition!\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)) {
fprintf(stderr, "ERROR: unable to append boot_args.\n");
}
}
add_reboot_reason(zbi);
if (watchdog_driver.flags & KDRV_GENERIC_32BIT_WATCHDOG_FLAG_ENABLED) {
start_meson_watchdog();
}
return 0;
}