blob: d495f3aacd5af5f7a78e62cc829374252b738bcc [file]
/*
* SoC First Mutable Code (FMC) and Flattened Image Tree (FIT)
* image parsing and loading.
*
* Copyright 2025 Google LLC
* Copyright (C) ASPEED Technology Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <string.h>
#include <libfdt.h>
#include <uart_console.h>
#include <io.h>
#include <image.h>
#include <fmc_image.h>
#include <ssp_tsp.h>
#include <platform_ast2700.h>
#define DEBUG 0
#define FIT_SEARCH_START (ASPEED_FMC_CS0_BASE)
#define FIT_SEARCH_END (FIT_SEARCH_START + 0x400000)
#define FIT_SEARCH_STEP 0x10000
extern void panic(const char *);
static bool has_sspfw;
static bool has_tspfw;
static uint64_t read_fit_address(const void *fdt, int node,
const char *prop_name)
{
const char *node_name;
const fdt32_t *prop;
uint32_t addr_upper;
uint32_t addr_lower;
int len;
if (!prop_name) {
uprintf("Invalid: prop_name is NULL!\n");
return 0;
}
node_name = fdt_get_name(fdt, node, NULL);
if (!node_name) {
node_name = "<unknown>";
}
prop = fdt_getprop(fdt, node, prop_name, &len);
if (!prop) {
uprintf("[%s] %s: property not found\n", node_name, prop_name);
return 0;
}
if (len == 4) {
return (uint64_t)fdt32_to_cpu(prop[0]);
} else if (len == 8) {
addr_upper = fdt32_to_cpu(prop[0]);
addr_lower = fdt32_to_cpu(prop[1]);
return ((uint64_t)addr_upper << 32) | addr_lower;
} else {
uprintf("[%s] %s: invalid length (%d bytes, expected 4 or 8)\n",
node_name, prop_name, len);
return 0;
}
}
#if DEBUG
static void dump_fit_image(const void *fit_blob)
{
uintptr_t data_offset;
int images_offset;
const void *data;
int node_offset;
const char *str;
uint64_t addr;
int data_len;
images_offset = fdt_path_offset(fit_blob, "/images");
if (images_offset < 0) {
uprintf("No /images node found\n");
return;
}
fdt_for_each_subnode(node_offset, fit_blob, images_offset) {
str = fdt_get_name(fit_blob, node_offset, NULL);
uprintf("Image node: %s\n", str);
str = fdt_getprop(fit_blob, node_offset, "description", NULL);
if (str) {
uprintf(" description: %s\n", str);
}
addr = read_fit_address(fit_blob, node_offset, "load");
if (addr) {
uprintf(" load address: 0x%016llx\n", (unsigned long long)addr);
}
addr = read_fit_address(fit_blob, node_offset, "entry");
if (addr) {
uprintf(" entry point: 0x%016llx\n", (unsigned long long)addr);
}
str = fdt_getprop(fit_blob, node_offset, "type", NULL);
if (str) {
uprintf(" type: %s\n", str);
}
str = fdt_getprop(fit_blob, node_offset, "os", NULL);
if (str) {
uprintf(" os: %s\n", str);
}
str = fdt_getprop(fit_blob, node_offset, "arch", NULL);
if (str) {
uprintf(" arch: %s\n", str);
}
str = fdt_getprop(fit_blob, node_offset, "compression", NULL);
if (str) {
uprintf(" compression: %s\n", str);
}
data = fdt_getprop(fit_blob, node_offset, "data", &data_len);
if (data && data_len > 0) {
data_offset = (uintptr_t)data - (uintptr_t)fit_blob;
uprintf(" data: %d bytes @ offset 0x%lx\n",
data_len, data_offset);
} else {
uprintf(" data: not found or empty\n");
}
}
}
#endif
/*
* Loads the "U-Boot" image from the FIT and returns its end address in memory.
*
* This function must be called before loading other images because some images,
* such as "fdt", may not have an explicit "load" property defined. In those
* cases, their load address is determined by placing them immediately after the
* u-boot image in memory.
*
* Loading U-Boot first ensures a well-defined memory layout baseline for
* subsequent images that rely on relative placement.
*/
static uint64_t load_uboot_image(const void *fit_blob)
{
uintptr_t data_offset;
uint64_t load_addr;
uint64_t dram_addr;
int images_offset;
const void *data;
const char *name;
int node_offset;
int data_len;
images_offset = fdt_path_offset(fit_blob, "/images");
if (images_offset < 0) {
uprintf("No /images node found\n");
return 0;
}
fdt_for_each_subnode(node_offset, fit_blob, images_offset) {
name = fdt_get_name(fit_blob, node_offset, NULL);
if (strcmp(name, "uboot") != 0) {
continue;
}
load_addr = read_fit_address(fit_blob, node_offset, "load");
if (!load_addr) {
uprintf("[%s] has no load address!\n", name);
return 0;
}
uprintf("[%s] load address: 0x%lx\n", name,
(unsigned long long)load_addr);
data = fdt_getprop(fit_blob, node_offset, "data", &data_len);
if (!data || data_len <= 0) {
uprintf("[%s] has no data!\n", name);
return 0;
}
data_offset = (uintptr_t)data - (uintptr_t)fit_blob;
uprintf("[%s] load end address: 0x%lx\n", name,
(unsigned long long)(load_addr + data_len));
uprintf("[%s] data: %d bytes @ offset 0x%lx\n", name, data_len,
data_offset);
dram_addr = convert_mcu_addr_to_arm_dram(load_addr);
uprintf("[%s] loading %d bytes to 0x%lx ... ", name, data_len,
(unsigned long long)dram_addr);
memcpy((void *)(uintptr_t)dram_addr, data, data_len);
uprintf("done\n");
return load_addr + data_len;
}
uprintf("[uboot] not found in FIT\n");
return 0;
}
/*
* Loads all images from the FIT except "U-Boot".
*
* If an image has a "load" property, its value is remapped to the 64-bit DRAM
* address space and used directly. If the image is "fdt" and does not have a
* "load" property, it is placed immediately after the U-Boot image, using the
* given uboot_end address as its load location.
*
* This function depends on uboot_end being correctly computed in advance by
* load_uboot_image(). This ensures a clean and predictable memory layout even
* if some images do not define explicit load addresses in the FIT.
*/
static void load_other_fit_images(const void *fit_blob, uint64_t uboot_end,
uint64_t *dest_addr,
const struct reserved_mem_info *info)
{
uintptr_t data_offset;
uint64_t load_addr;
uint64_t dram_addr;
int images_offset;
const void *data;
const char *name;
int node_offset;
int data_len;
images_offset = fdt_path_offset(fit_blob, "/images");
if (images_offset < 0) {
uprintf("No /images node found\n");
return;
}
fdt_for_each_subnode(node_offset, fit_blob, images_offset) {
name = fdt_get_name(fit_blob, node_offset, NULL);
/* Skip U-Boot, which should already be loaded */
if (strcmp(name, "uboot") == 0) {
continue;
}
data = fdt_getprop(fit_blob, node_offset, "data", &data_len);
if (!data || data_len <= 0) {
uprintf("[%s] skip: no data\n", name);
continue;
}
load_addr = read_fit_address(fit_blob, node_offset, "load");
if (load_addr) {
uprintf("[%s] load address: 0x%lx\n", name,
(unsigned long long)load_addr);
/* Image has explicit load address, remap for ARM DRAM view */
dram_addr = convert_mcu_addr_to_arm_dram(load_addr);
/* The next image to jump to is BL31 (Trusted Firmware-A) */
if (strcmp(name, "atf") == 0) {
*dest_addr = dram_addr;
}
/* Init co-processor */
if (strcmp(name, "sspfw") == 0) {
ssp_init(dram_addr, info);
has_sspfw = true;
}
if (strcmp(name, "tspfw") == 0) {
tsp_init(dram_addr, info);
has_tspfw = true;
}
} else if (strcmp(name, "fdt") == 0 && uboot_end) {
/* fdt has no load address, fallback to uboot_end */
load_addr = uboot_end;
dram_addr = convert_mcu_addr_to_arm_dram(load_addr);
uprintf("[%s] no load addr, fallback to u-boot end: 0x%lx\n",
name, (unsigned long long)load_addr);
} else {
uprintf("[%s] skip: no load address and no fallback\n", name);
continue;
}
data_offset = (uintptr_t)data - (uintptr_t)fit_blob;
uprintf("[%s] data: %d bytes @ offset 0x%lx\n",
name, data_len, data_offset);
uprintf("[%s] loading %d bytes to 0x%lx ... ",
name, data_len, (unsigned long long)dram_addr);
memcpy((void *)(uintptr_t)dram_addr, data, data_len);
uprintf("done\n");
}
}
static const void *find_fit_image(uint64_t start_addr, uint64_t end_addr,
uint64_t search_step)
{
const void *ptr = NULL;
int total_size;
uint64_t addr;
if (search_step == 0) {
uprintf("search_step cannot be zero.\n");
return NULL;
}
for (addr = start_addr; addr < end_addr; addr += search_step) {
ptr = (const void *)(uintptr_t)addr;
if (fdt_check_header(ptr) == 0) {
total_size = fdt_totalsize(ptr);
uprintf("Found valid FIT image at 0x%lx (size: 0x%x bytes)\n",
addr, total_size);
return ptr;
}
}
uprintf("No valid FIT image found in range 0x%lx - 0x%lx (step: 0x%lx)\n",
start_addr, end_addr, search_step);
return NULL;
}
static int find_fmc_image(uint64_t start_addr, uint64_t end_addr,
uint64_t search_step, struct fmc_img_info *info)
{
struct ast_fmc_header *hdr;
uint32_t fmc_header_size;
uint32_t payload_size;
uint32_t total_size;
uint64_t addr;
if (info == NULL) {
return 0;
}
fmc_header_size = sizeof(struct ast_fmc_header);
for (addr = start_addr;
addr + fmc_header_size <= end_addr;
addr += search_step) {
hdr = (struct ast_fmc_header *)addr;
if (hdr->preamble.magic == FMC_HDR_MAGIC) {
payload_size = hdr->body.size;
total_size = fmc_header_size + payload_size;
if (payload_size > 0 && (addr + total_size) <= end_addr) {
info->payload_start = addr + fmc_header_size;
info->payload_end = ALIGN_UP(addr + total_size, search_step);
uprintf("Found valid FMC v%d image at 0x%lx (size: 0x%x)",
hdr->preamble.version, addr, total_size);
uprintf(", next FIT search @ 0x%lx\n", info->payload_end);
return 1;
}
}
}
uprintf("No valid FMC image found in range 0x%lx - 0x%lx (step: 0x%lx)\n",
start_addr, end_addr, search_step);
return 0;
}
uint64_t load_fit_boot_image(void)
{
struct reserved_mem_info reservedinfo = {0};
struct fmc_img_info fmcinfo = {0};
uint64_t search_next_addr;
uint64_t bl31_addr = 0;
const void *fit_blob;
void *dtb_ptr = NULL;
uint64_t uboot_end;
search_next_addr = FIT_SEARCH_START;
/* Find FMC image */
if (find_fmc_image(search_next_addr, FIT_SEARCH_END, FIT_SEARCH_STEP,
&fmcinfo)) {
search_next_addr = fmcinfo.payload_end;
/* Try to find and load a valid SPL DTB between FMC and U-Boot FIT */
dtb_ptr = find_and_load_appended_dtb(fmcinfo.payload_start,
fmcinfo.payload_end);
if (dtb_ptr) {
get_reserved_memory(dtb_ptr, &reservedinfo);
}
}
/* Find U-Boot FIT imag */
fit_blob = find_fit_image(search_next_addr,
FIT_SEARCH_END,
FIT_SEARCH_STEP);
if (!fit_blob) {
panic("");
}
#if DEBUG
dump_fit_image(fit_blob);
#endif
uboot_end = load_uboot_image(fit_blob);
if (!uboot_end) {
panic("");
}
load_other_fit_images(fit_blob, uboot_end, &bl31_addr, &reservedinfo);
if (!bl31_addr) {
uprintf("Error: BL31 (Trusted Firmware-A) not found, halting.\n");
panic("");
}
if (has_sspfw) {
ssp_enable();
}
if (has_tspfw) {
tsp_enable();
}
uprintf("\nJumping to BL31 (Trusted Firmware-A) at 0x%lx\n\n",
bl31_addr);
return bl31_addr;
}