Introduce support for loading a Linux kernel Image (its compressed
version) on RISC-V.
kernel_image_load() and place_modules() currently call panic() on
failure rather than returning an error. This is because the common
kernel_load() in common/device-tree/kernel.c does not expect a
return code. Handling errors gracefully would require a separate
refactor.
The implementation is based on the Xen Arm kernel loading code.
Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>
---
Changes in v2:
- s/zimage/image as RISC-V doesn't support zImages, only Image and
Image.{gz,...}.
- Update the commit message.
---
xen/arch/riscv/Makefile | 1 +
xen/arch/riscv/include/asm/config.h | 13 +++
xen/arch/riscv/kernel.c | 158 ++++++++++++++++++++++++++++
3 files changed, 172 insertions(+)
create mode 100644 xen/arch/riscv/kernel.c
diff --git a/xen/arch/riscv/Makefile b/xen/arch/riscv/Makefile
index 6d3c822409b8..d90afc7ad23f 100644
--- a/xen/arch/riscv/Makefile
+++ b/xen/arch/riscv/Makefile
@@ -8,6 +8,7 @@ obj-y += guestcopy.o
obj-y += imsic.o
obj-y += intc.o
obj-y += irq.o
+obj-y += kernel.o
obj-y += mm.o
obj-y += p2m.o
obj-y += paging.o
diff --git a/xen/arch/riscv/include/asm/config.h b/xen/arch/riscv/include/asm/config.h
index 0613de008b13..fd69057826e1 100644
--- a/xen/arch/riscv/include/asm/config.h
+++ b/xen/arch/riscv/include/asm/config.h
@@ -151,6 +151,19 @@
extern unsigned long phys_offset; /* = load_start - XEN_VIRT_START */
#endif
+/*
+ * KERNEL_LOAD_ADDR_ALIGNMENT is defined based on paragraph of
+ * "Kernel location" of boot.rst:
+ * https://docs.kernel.org/arch/riscv/boot.html#kernel-location
+ */
+#if defined(CONFIG_RISCV_32)
+#define KERNEL_LOAD_ADDR_ALIGNMENT MB(4)
+#elif defined(CONFIG_RISCV_64)
+#define KERNEL_LOAD_ADDR_ALIGNMENT MB(2)
+#else
+#error "Define KERNEL_LOAD_ADDR_ALIGNMENT"
+#endif
+
#endif /* ASM__RISCV__CONFIG_H */
/*
* Local variables:
diff --git a/xen/arch/riscv/kernel.c b/xen/arch/riscv/kernel.c
new file mode 100644
index 000000000000..be5b17dc22c3
--- /dev/null
+++ b/xen/arch/riscv/kernel.c
@@ -0,0 +1,158 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#include <xen/bug.h>
+#include <xen/compiler.h>
+#include <xen/errno.h>
+#include <xen/fdt-kernel.h>
+#include <xen/guest_access.h>
+#include <xen/init.h>
+#include <xen/libfdt/libfdt.h>
+#include <xen/mm.h>
+#include <xen/types.h>
+#include <xen/vmap.h>
+
+#include <asm/setup.h>
+
+#define IMAGE64_MAGIC_V2 0x05435352 /* Magic number 2, le, "RSC\x05" */
+
+static void __init place_modules(struct kernel_info *info, paddr_t kernbase,
+ paddr_t kernend)
+{
+ const struct boot_module *mod = info->bd.initrd;
+
+ const paddr_t initrd_len = ROUNDUP(mod ? mod->size : 0, MB(2));
+ const paddr_t dtb_len = ROUNDUP(fdt_totalsize(info->fdt), MB(2));
+ const paddr_t modsize = initrd_len + dtb_len;
+
+ const paddr_t ramsize = info->mem.bank[0].size;
+ const paddr_t kernsize = ROUNDUP(kernend, MB(2)) - kernbase;
+
+ if ( modsize + kernsize > ramsize )
+ panic("Not enough memory in the first bank for the kernel+dtb+initrd\n");
+
+ info->dtb_paddr = ROUNDUP(kernend, MB(2));
+
+ info->initrd_paddr = info->dtb_paddr + dtb_len;
+}
+
+static paddr_t __init kernel_image_place(struct kernel_info *info)
+{
+ paddr_t load_addr;
+
+ /*
+ * At the moment, RISC-V's Linux kernel should be always position
+ * independent based on "Per-MMU execution" of boot.rst:
+ * https://docs.kernel.org/arch/riscv/boot.html#pre-mmu-execution
+ *
+ * But just for the case when RISC-V's Linux kernel isn't position
+ * independent it is needed to take load address from
+ * info->image.start.
+ *
+ * If `start` is zero, the Image is position independent. */
+ if ( likely(!info->image.start) )
+ /*
+ * According to boot.rst kernel load address should be properly
+ * aligned:
+ * https://docs.kernel.org/arch/riscv/boot.html#kernel-location
+ */
+ load_addr = ROUNDUP(info->mem.bank[0].start, KERNEL_LOAD_ADDR_ALIGNMENT);
+ else
+ load_addr = info->image.start;
+
+ return load_addr;
+}
+
+static void __init kernel_image_load(struct kernel_info *info)
+{
+ int rc;
+ paddr_t load_addr = kernel_image_place(info);
+ paddr_t paddr = info->image.kernel_addr;
+ paddr_t len = info->image.len;
+ void *kernel;
+
+ info->entry = load_addr;
+
+ place_modules(info, load_addr, load_addr + len);
+
+ printk("Loading Image from %"PRIpaddr" to %"PRIpaddr"-%"PRIpaddr"\n",
+ paddr, load_addr, load_addr + len);
+
+ kernel = ioremap_wc(paddr, len);
+
+ if ( !kernel )
+ panic("Unable to map kernel\n");
+
+ /* Move kernel to proper location in guest phys map */
+ rc = copy_to_guest_phys(info->bd.d, load_addr, kernel, len);
+
+ if ( rc )
+ panic("Unable to copy kernel to proper guest location\n");
+
+ iounmap(kernel);
+}
+
+/* Check if the image is a 64-bit Image */
+static int __init kernel_image64_probe(struct kernel_info *info,
+ paddr_t addr, paddr_t size)
+{
+ /* riscv/boot-image-header.rst */
+ struct {
+ u32 code0; /* Executable code */
+ u32 code1; /* Executable code */
+ u64 text_offset; /* Image load offset, little endian */
+ u64 image_size; /* Effective Image size, little endian */
+ u64 flags; /* kernel flags, little endian */
+ u32 version; /* Version of this header */
+ u32 res1; /* Reserved */
+ u64 res2; /* Reserved */
+ u64 magic; /* Deprecated: Magic number, little endian, "RISCV" */
+ u32 magic2; /* Magic number 2, little endian, "RSC\x05" */
+ u32 res3; /* Reserved for PE COFF offset */
+ } image;
+ uint64_t start, end;
+
+ if ( size < sizeof(image) )
+ return -EINVAL;
+
+ copy_from_paddr(&image, addr, sizeof(image));
+
+ /* Magic v1 is deprecated and may be removed. Only use v2 */
+ if ( image.magic2 != IMAGE64_MAGIC_V2 )
+ return -EINVAL;
+
+ /* Currently there is no length in the header, so just use the size */
+ start = 0;
+ end = size;
+
+ /*
+ * Given the above this check is a bit pointless, but leave it
+ * here in case someone adds a length field in the future.
+ */
+ if ( (end - start) > size )
+ return -EINVAL;
+
+ info->image.kernel_addr = addr;
+ info->image.len = end - start;
+ info->image.text_offset = image.text_offset;
+ info->image.start = 0;
+
+ info->load = kernel_image_load;
+
+ return 0;
+}
+
+int __init kernel_image_probe(struct kernel_info *info, paddr_t addr,
+ paddr_t size)
+{
+ int rc;
+
+#ifdef CONFIG_RISCV_64
+ rc = kernel_image64_probe(info, addr, size);
+ if ( rc < 0 )
+ panic("Failed to probe kernel image\n");
+#else
+ panic("Only RISC-V 64 is supported\n");
+#endif
+
+ return rc;
+}
--
2.53.0