arch/riscv: Enable FIT support

Tested on qemu-riscv.
Depends on OpenSBI integration and proper memory detection in qemu.

Boots into Linux until initrd should be loaded.

Tested on SiFive/unleashed:
Boots into Linux until earlycon terminates.

Change-Id: I5ebc6cc2cc9e328f36d70fba13555386bb8c29d6
Signed-off-by: Jonathan Neuschäfer <j.neuschaefer@gmx.net>
Signed-off-by: Patrick Rudolph <patrick.rudolph@9elements.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/30292
Reviewed-by: Philipp Deppenwiese <zaolin.daisuki@gmail.com>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
diff --git a/Documentation/lib/payloads/fit.md b/Documentation/lib/payloads/fit.md
index 24807bf..57a1a54 100644
--- a/Documentation/lib/payloads/fit.md
+++ b/Documentation/lib/payloads/fit.md
@@ -6,6 +6,7 @@
 ## Supported architectures
 
 * aarch64
+* riscv
 
 ## Supported FIT sections
 
@@ -24,6 +25,7 @@
 ## Architecture specifics
 
 The FIT parser needs architecure support.
+
 ### aarch64
 The source code can be found in `src/arch/arm64/fit_payload.c`.
 
@@ -31,6 +33,13 @@
 format and it needs a devicetree (a section named 'fdt') to boot.
 The kernel will be placed close to "*DRAMSTART*".
 
+### RISC-V
+The source code can be found in `src/arch/riscv/fit_payload.c`.
+
+On RISC-V the kernel (a section named 'kernel') must be in **Image**
+format and it needs a devicetree (a section named 'fdt') to boot.
+The kernel will be placed close to "*DRAMSTART*".
+
 ### Other
 Other architectures aren't supported.
 
diff --git a/payloads/Kconfig b/payloads/Kconfig
index d0f8a44..46cfaf5 100644
--- a/payloads/Kconfig
+++ b/payloads/Kconfig
@@ -30,7 +30,7 @@
 
 config PAYLOAD_FIT
 	bool "A FIT payload"
-	depends on ARCH_ARM64
+	depends on ARCH_ARM64 || ARCH_RISCV
 	select PAYLOAD_FIT_SUPPORT
 	help
 	  Select this option if you have a payload image (a FIT file) which
@@ -99,8 +99,8 @@
 config PAYLOAD_FIT_SUPPORT
 	bool "FIT support"
 	default n
-	default y if PAYLOAD_LINUX && (ARCH_ARM || ARCH_ARM64)
-	depends on ARCH_ARM64
+	default y if PAYLOAD_LINUX && (ARCH_ARM || ARCH_ARM64 || ARCH_RISCV)
+	depends on ARCH_ARM64 || ARCH_RISCV
 	select FLATTENED_DEVICE_TREE
 	help
 	  Select this option if your payload is of type FIT.
diff --git a/payloads/external/LinuxBoot/Kconfig b/payloads/external/LinuxBoot/Kconfig
index 84af49c..a91288b 100644
--- a/payloads/external/LinuxBoot/Kconfig
+++ b/payloads/external/LinuxBoot/Kconfig
@@ -39,6 +39,13 @@
 	help
 	  AARCH64 kernel and initramfs
 
+config LINUXBOOT_RISCV
+	bool "RISC-V"
+	depends on ARCH_RISCV
+	select PAYLOAD_FIT_SUPPORT
+	help
+	  RISC-V kernel and initramfs
+
 endchoice
 
 comment "Linux kernel"
@@ -126,7 +133,7 @@
 choice
 	prompt "Kernel binary format"
 	default LINUXBOOT_KERNEL_BZIMAGE if LINUXBOOT_X86 || LINUXBOOT_X86_64
-	default LINUXBOOT_KERNEL_UIMAGE if LINUXBOOT_ARM64
+	default LINUXBOOT_KERNEL_UIMAGE if LINUXBOOT_ARM64 || LINUXBOOT_RISCV
 
 config LINUXBOOT_KERNEL_BZIMAGE
 	bool "bzImage"
@@ -134,14 +141,14 @@
 
 config LINUXBOOT_KERNEL_UIMAGE
 	bool "uImage"
-	depends on LINUXBOOT_ARM64
+	depends on LINUXBOOT_ARM64 || LINUXBOOT_RISCV
 
 endchoice
 
 
 config LINUXBOOT_DTB_FILE
 	string "Compiled devicetree file"
-	depends on LINUXBOOT_ARM64
+	depends on LINUXBOOT_ARM64 || LINUXBOOT_RISCV
 	default ""
 
 endif #LINUXBOOT_COMPILE_KERNEL
@@ -154,7 +161,7 @@
 
 config PAYLOAD_FILE
 	default "payloads/external/LinuxBoot/linuxboot/bzImage" if LINUXBOOT_COMPILE_KERNEL && ( LINUXBOOT_X86 || LINUXBOOT_X86_64 )
-	default "payloads/external/LinuxBoot/linuxboot/uImage" if LINUXBOOT_COMPILE_KERNEL && LINUXBOOT_ARM64
+	default "payloads/external/LinuxBoot/linuxboot/uImage" if LINUXBOOT_COMPILE_KERNEL && (LINUXBOOT_ARM64 || LINUXBOOT_RISCV)
 	default LINUXBOOT_KERNEL_PATH if !LINUXBOOT_COMPILE_KERNEL
 
 comment "Linux initramfs"
diff --git a/payloads/external/LinuxBoot/Kconfig.name b/payloads/external/LinuxBoot/Kconfig.name
index 18438c7..c59a8bc 100644
--- a/payloads/external/LinuxBoot/Kconfig.name
+++ b/payloads/external/LinuxBoot/Kconfig.name
@@ -14,7 +14,7 @@
 
 config PAYLOAD_LINUXBOOT
 	bool "LinuxBoot"
-	depends on ARCH_X86 || ARCH_ARM64
+	depends on ARCH_X86 || ARCH_ARM64 || ARCH_RISCV
 	help
 	  Select this option if you want to build a coreboot image
 	  with a LinuxBoot payload. If you don't know what this is
diff --git a/payloads/external/linux/Kconfig.name b/payloads/external/linux/Kconfig.name
index 63621d8..493eb98 100644
--- a/payloads/external/linux/Kconfig.name
+++ b/payloads/external/linux/Kconfig.name
@@ -1,6 +1,6 @@
 config PAYLOAD_LINUX
 	bool "A Linux payload"
-	depends on ARCH_X86 || ARCH_ARM
+	depends on ARCH_X86 || ARCH_ARM || ARCH_RISCV
 	help
 	  Select this option if you have a Linux bzImage which coreboot
 	  should run as soon as the basic hardware initialization
diff --git a/src/arch/riscv/Makefile.inc b/src/arch/riscv/Makefile.inc
index 0116859..0039fab 100644
--- a/src/arch/riscv/Makefile.inc
+++ b/src/arch/riscv/Makefile.inc
@@ -147,6 +147,7 @@
 ramstage-y += tables.c
 ramstage-y += payload.c
 ramstage-$(ARCH_RISCV_PMP) += pmp.c
+ramstage-y += fit_payload.c
 ramstage-y += \
 	$(top)/src/lib/memchr.c \
 	$(top)/src/lib/memcmp.c \
diff --git a/src/arch/riscv/fit_payload.c b/src/arch/riscv/fit_payload.c
new file mode 100644
index 0000000..60a4bc0
--- /dev/null
+++ b/src/arch/riscv/fit_payload.c
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2013 Google Inc.
+ * Copyright 2018 Facebook, Inc.
+ * Copyright 2019 9elements Agency GmbH <patrick.rudolph@9elements.com>
+ *
+ * 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>
+
+/* Implements a Berkley Boot Loader (BBL) compatible payload loading */
+
+#define MAX_KERNEL_SIZE (64*MiB)
+
+#if CONFIG(ARCH_RISCV_RV32)
+#define SECTION_ALIGN (4 * MiB)
+#endif
+#if CONFIG(ARCH_RISCV_RV64)
+#define SECTION_ALIGN (2 * MiB)
+#endif
+
+static size_t get_kernel_size(const struct fit_image_node *node)
+{
+	/*
+	 * Since we don't have a way to determine the uncompressed size of the
+	 * kernel, we have 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_INFO,
+	       "FIT: Leaving additional %u MiB of free space after kernel.\n",
+	       MAX_KERNEL_SIZE >> 20);
+
+	return node->size + MAX_KERNEL_SIZE;
+}
+
+/**
+ * Place the region in free memory range.
+ *
+ * The caller has to set region->offset to the minimum allowed address.
+ */
+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;
+
+	/* Section must be aligned at page boundary */
+	start = ALIGN_UP(MAX(region->offset, range_entry_base(r)), SECTION_ALIGN);
+
+	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)
+{
+	void *arg = NULL;
+
+	if (!config->fdt || !fdt) {
+		printk(BIOS_CRIT, "CRIT: Providing a valid FDT is mandatory to "
+		       "boot a RISC-V kernel!\n");
+		return false;
+		/* TODO: Fall back to the ROM FDT? */
+	}
+
+	/* Update kernel size from image header, if possible */
+	kernel->size = get_kernel_size(config->kernel);
+	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.
+	 */
+	kernel->offset = 0;
+	if (!bootmem_walk(fit_place_mem, kernel))
+		return false;
+
+	/* Mark as reserved for future allocations. */
+	bootmem_add_range(kernel->offset, kernel->size, BM_MEM_PAYLOAD);
+
+	/* Place FDT and INITRD after kernel. */
+
+	/* Place INITRD */
+	if (config->ramdisk) {
+		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 */
+	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;
+}