summaryrefslogtreecommitdiff
path: root/src/arch/arm64
diff options
context:
space:
mode:
Diffstat (limited to 'src/arch/arm64')
-rw-r--r--src/arch/arm64/Makefile.inc1
-rw-r--r--src/arch/arm64/fit_payload.c262
2 files changed, 263 insertions, 0 deletions
diff --git a/src/arch/arm64/Makefile.inc b/src/arch/arm64/Makefile.inc
index f57ef720b9..3c3cd20370 100644
--- a/src/arch/arm64/Makefile.inc
+++ b/src/arch/arm64/Makefile.inc
@@ -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
diff --git a/src/arch/arm64/fit_payload.c b/src/arch/arm64/fit_payload.c
new file mode 100644
index 0000000000..c4bbceee4c
--- /dev/null
+++ b/src/arch/arm64/fit_payload.c
@@ -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;
+}