lib: Add FIT payload support
* Add support for parsing and booting FIT payloads. * Build fit loader code from depthcharge. * Fix coding style. * Add Kconfig option to add compiletime support for FIT. * Add support for initrd. * Add default compat strings * Apply optional devicetree fixups using dt_apply_fixups Starting at this point the CBFS payload/ can be either SELF or FIT. Tested on Cavium SoC: Parses and loads a Linux kernel 4.16.3. Tested on Cavium SoC: Parses and loads a Linux kernel 4.15.0. Tested on Cavium SoC: Parses and loads a Linux kernel 4.1.52. Change-Id: I0f27b92a5e074966f893399eb401eb97d784850d Signed-off-by: Patrick Rudolph <patrick.rudolph@9elements.com> Reviewed-on: https://review.coreboot.org/25019 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Philipp Deppenwiese <zaolin.daisuki@gmail.com>
This commit is contained in:
committed by
Philipp Deppenwiese
parent
8c986ab263
commit
a892cde653
@@ -136,6 +136,7 @@ ramstage-y += memcpy.S
|
||||
ramstage-y += memmove.S
|
||||
ramstage-$(CONFIG_ARM64_USE_ARM_TRUSTED_FIRMWARE) += arm_tf.c
|
||||
ramstage-y += transition.c transition_asm.S
|
||||
ramstage-$(CONFIG_PAYLOAD_FIT_SUPPORT) += fit_payload.c
|
||||
|
||||
rmodules_arm64-y += memset.S
|
||||
rmodules_arm64-y += memcpy.S
|
||||
|
||||
262
src/arch/arm64/fit_payload.c
Normal file
262
src/arch/arm64/fit_payload.c
Normal file
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
* Copyright 2018 Facebook, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <console/console.h>
|
||||
#include <bootmem.h>
|
||||
#include <stdlib.h>
|
||||
#include <program_loading.h>
|
||||
#include <string.h>
|
||||
#include <commonlib/compression.h>
|
||||
#include <commonlib/cbfs_serialized.h>
|
||||
#include <lib.h>
|
||||
#include <fit.h>
|
||||
#include <endian.h>
|
||||
|
||||
#define MAX_KERNEL_SIZE (64*MiB)
|
||||
|
||||
struct arm64_kernel_header {
|
||||
u32 code0;
|
||||
u32 code1;
|
||||
u64 text_offset;
|
||||
u64 image_size;
|
||||
u64 flags;
|
||||
u64 res2;
|
||||
u64 res3;
|
||||
u64 res4;
|
||||
u32 magic;
|
||||
#define KERNEL_HEADER_MAGIC 0x644d5241
|
||||
u32 res5;
|
||||
};
|
||||
|
||||
static struct {
|
||||
union {
|
||||
struct arm64_kernel_header header;
|
||||
u8 raw[sizeof(struct arm64_kernel_header) + 0x100];
|
||||
};
|
||||
#define SCRATCH_CANARY_VALUE 0xdeadbeef
|
||||
u32 canary;
|
||||
} scratch;
|
||||
|
||||
/* Returns true if decompressing was successful and it looks like a kernel. */
|
||||
static bool decompress_kernel_header(const struct fit_image_node *node)
|
||||
{
|
||||
/* Partially decompress to get text_offset. Can't check for errors. */
|
||||
scratch.canary = SCRATCH_CANARY_VALUE;
|
||||
switch (node->compression) {
|
||||
case CBFS_COMPRESS_NONE:
|
||||
memcpy(scratch.raw, node->data, sizeof(scratch.raw));
|
||||
break;
|
||||
case CBFS_COMPRESS_LZMA:
|
||||
ulzman(node->data, node->size,
|
||||
scratch.raw, sizeof(scratch.raw));
|
||||
break;
|
||||
case CBFS_COMPRESS_LZ4:
|
||||
ulz4fn(node->data, node->size,
|
||||
scratch.raw, sizeof(scratch.raw));
|
||||
break;
|
||||
default:
|
||||
printk(BIOS_ERR, "ERROR: Unsupported compression algorithm!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Should never happen, but if it does we'll want to know. */
|
||||
if (scratch.canary != SCRATCH_CANARY_VALUE)
|
||||
die("ERROR: Partial decompression ran over scratchbuf!\n");
|
||||
|
||||
if (scratch.header.magic != KERNEL_HEADER_MAGIC) {
|
||||
printk(BIOS_ERR,
|
||||
"ERROR: Invalid kernel magic: %#.8x\n != %#.8x\n",
|
||||
scratch.header.magic, KERNEL_HEADER_MAGIC);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prior to v3.17, the endianness of text_offset was not specified. In
|
||||
* these cases image_size is zero and text_offset is 0x80000 in the
|
||||
* endianness of the kernel. Where image_size is non-zero image_size is
|
||||
* little-endian and must be respected. Where image_size is zero,
|
||||
* text_offset can be assumed to be 0x80000.
|
||||
*/
|
||||
if (!scratch.header.image_size)
|
||||
scratch.header.text_offset = cpu_to_le64(0x80000);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static size_t get_kernel_size(const struct fit_image_node *node)
|
||||
{
|
||||
if (scratch.header.image_size)
|
||||
return le64_to_cpu(scratch.header.image_size);
|
||||
|
||||
/**
|
||||
* When image_size is zero, a bootloader should attempt to keep as much
|
||||
* memory as possible free for use by the kernel immediately after the
|
||||
* end of the kernel image. The amount of space required will vary
|
||||
* depending on selected features, and is effectively unbound.
|
||||
*/
|
||||
|
||||
printk(BIOS_WARNING, "FIT: image_size not set in kernel header.\n"
|
||||
"Leaving additional %u MiB of free space after kernel.\n",
|
||||
MAX_KERNEL_SIZE >> 20);
|
||||
|
||||
return node->size + MAX_KERNEL_SIZE;
|
||||
}
|
||||
|
||||
static bool fit_place_kernel(const struct range_entry *r, void *arg)
|
||||
{
|
||||
struct region *region = arg;
|
||||
resource_t start;
|
||||
|
||||
if (range_entry_tag(r) != BM_MEM_RAM)
|
||||
return true;
|
||||
|
||||
/**
|
||||
* The Image must be placed text_offset bytes from a 2MB aligned base
|
||||
* address anywhere in usable system RAM and called there. The region
|
||||
* between the 2 MB aligned base address and the start of the image has
|
||||
* no special significance to the kernel, and may be used for other
|
||||
* purposes.
|
||||
*
|
||||
* If the reserved memory (BL31 for example) is smaller than text_offset
|
||||
* we can use the 2 MiB base address, otherwise use the next 2 MiB page.
|
||||
* It's not mandatory, but wastes less memory below the kernel.
|
||||
*/
|
||||
start = ALIGN_DOWN(range_entry_base(r), 2 * MiB) +
|
||||
le64_to_cpu(scratch.header.text_offset);
|
||||
|
||||
if (start < range_entry_base(r))
|
||||
start += 2 * MiB;
|
||||
/**
|
||||
* At least image_size bytes from the start of the image must be free
|
||||
* for use by the kernel.
|
||||
*/
|
||||
if (start + region->size < range_entry_end(r)) {
|
||||
region->offset = (size_t)start;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Place the region in free memory range.
|
||||
*
|
||||
* The caller has to set region->offset to the minimum allowed address.
|
||||
* The region->offset is usually 0 on kernel >v4.6 and kernel_base + kernel_size
|
||||
* on kernel <v4.6.
|
||||
*/
|
||||
static bool fit_place_mem(const struct range_entry *r, void *arg)
|
||||
{
|
||||
struct region *region = arg;
|
||||
resource_t start;
|
||||
|
||||
if (range_entry_tag(r) != BM_MEM_RAM)
|
||||
return true;
|
||||
|
||||
/* Linux 4.15 doesn't like 4KiB alignment. Align to 1 MiB for now. */
|
||||
start = ALIGN_UP(MAX(region->offset, range_entry_base(r)), 1 * MiB);
|
||||
|
||||
if (start + region->size < range_entry_end(r)) {
|
||||
region->offset = (size_t)start;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool fit_payload_arch(struct prog *payload, struct fit_config_node *config,
|
||||
struct region *kernel,
|
||||
struct region *fdt,
|
||||
struct region *initrd)
|
||||
{
|
||||
bool place_anywhere;
|
||||
void *arg = NULL;
|
||||
|
||||
if (!config->fdt || !fdt) {
|
||||
printk(BIOS_CRIT, "CRIT: Providing a valid FDT is mandatory to "
|
||||
"boot an ARM64 kernel!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!decompress_kernel_header(config->kernel_node)) {
|
||||
printk(BIOS_CRIT, "CRIT: Payload doesn't look like an ARM64"
|
||||
" kernel Image.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Update kernel size from image header, if possible */
|
||||
kernel->size = get_kernel_size(config->kernel_node);
|
||||
printk(BIOS_DEBUG, "FIT: Using kernel size of 0x%zx bytes\n",
|
||||
kernel->size);
|
||||
|
||||
/**
|
||||
* The code assumes that bootmem_walk provides a sorted list of memory
|
||||
* regions, starting from the lowest address.
|
||||
* The order of the calls here doesn't matter, as the placement is
|
||||
* enforced in the called functions.
|
||||
* For details check code on top.
|
||||
*/
|
||||
|
||||
if (!bootmem_walk(fit_place_kernel, kernel))
|
||||
return false;
|
||||
|
||||
/* Mark as reserved for future allocations. */
|
||||
bootmem_add_range(kernel->offset, kernel->size, BM_MEM_PAYLOAD);
|
||||
|
||||
/**
|
||||
* NOTE: versions prior to v4.6 cannot make use of memory below the
|
||||
* physical offset of the Image so it is recommended that the Image be
|
||||
* placed as close as possible to the start of system RAM.
|
||||
*
|
||||
* For kernel <v4.6 the INITRD and FDT can't be placed below the kernel.
|
||||
* In that case set region offset to an address on top of kernel.
|
||||
*/
|
||||
place_anywhere = !!(le64_to_cpu(scratch.header.flags) & (1 << 3));
|
||||
printk(BIOS_DEBUG, "FIT: Placing FDT and INITRD %s\n",
|
||||
place_anywhere ? "anywhere" : "on top of kernel");
|
||||
|
||||
/* Place INITRD */
|
||||
if (config->ramdisk) {
|
||||
if (place_anywhere)
|
||||
initrd->offset = 0;
|
||||
else
|
||||
initrd->offset = kernel->offset + kernel->size;
|
||||
|
||||
if (!bootmem_walk(fit_place_mem, initrd))
|
||||
return false;
|
||||
/* Mark as reserved for future allocations. */
|
||||
bootmem_add_range(initrd->offset, initrd->size, BM_MEM_PAYLOAD);
|
||||
}
|
||||
|
||||
/* Place FDT */
|
||||
if (place_anywhere)
|
||||
fdt->offset = 0;
|
||||
else
|
||||
fdt->offset = kernel->offset + kernel->size;
|
||||
|
||||
if (!bootmem_walk(fit_place_mem, fdt))
|
||||
return false;
|
||||
/* Mark as reserved for future allocations. */
|
||||
bootmem_add_range(fdt->offset, fdt->size, BM_MEM_PAYLOAD);
|
||||
|
||||
/* Kernel expects FDT as argument */
|
||||
arg = (void *)fdt->offset;
|
||||
|
||||
prog_set_entry(payload, (void *)kernel->offset, arg);
|
||||
|
||||
bootmem_dump_ranges();
|
||||
|
||||
return true;
|
||||
}
|
||||
Reference in New Issue
Block a user