arm: init: save previous bootloader data
authorDzmitry Sankouski <dsankouski@gmail.com>
Tue, 22 Feb 2022 18:49:52 +0000 (21:49 +0300)
committerTom Rini <trini@konsulko.com>
Mon, 4 Apr 2022 18:53:26 +0000 (14:53 -0400)
When u-boot is used as a chain-loaded bootloader (replacing OS kernel),
previous bootloader leaves data in RAM, that can be reused.

For example, on recent arm linux system, when chainloading u-boot,
there are initramfs and fdt in RAM prepared for OS booting. Initramfs
may be modified to store u-boot's payload, thus providing the ability to
use chainloaded u-boot to boot OS without any storage support.

Two config options added:
- SAVE_PREV_BL_INITRAMFS_START_ADDR
  saves initramfs start address to 'prevbl_initrd_start_addr' environment
  variable
- SAVE_PREV_BL_FDT_ADDR
  saves fdt address to 'prevbl_fdt_addr' environment variable

Signed-off-by: Dzmitry Sankouski <dsankouski@gmail.com>
Cc: Tom Rini <trini@konsulko.com>
arch/arm/lib/Makefile
arch/arm/lib/save_prev_bl_data.c [new file with mode: 0644]
boot/Kconfig
common/board_r.c
include/init.h

index 594fc1228ae53b2e25f4a818510f1ec5ed0b88f4..c603fe61bc403e7771b34069685f30a97b93f394 100644 (file)
@@ -48,6 +48,11 @@ obj-$(CONFIG_$(SPL_TPL_)USE_ARCH_MEMCPY) += memcpy.o
 endif
 obj-$(CONFIG_$(SPL_TPL_)SEMIHOSTING) += semihosting.o
 
+ifneq ($(filter y,$(CONFIG_SAVE_PREV_BL_INITRAMFS_START_ADDR) $(CONFIG_SAVE_PREV_BL_FDT_ADDR)),)
+obj-y += save_prev_bl_data.o
+endif
+
+# obj-$(CONFIG_SAVE_PREV_BL_INITRAMFS_START_ADDR) += save_prev_bl_data.o
 obj-y  += bdinfo.o
 obj-y  += sections.o
 CFLAGS_REMOVE_sections.o := $(LTO_CFLAGS)
diff --git a/arch/arm/lib/save_prev_bl_data.c b/arch/arm/lib/save_prev_bl_data.c
new file mode 100644 (file)
index 0000000..f4ee86a
--- /dev/null
@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * save_prev_bl_data - saving previous bootloader data
+ * to environment variables.
+ *
+ * Copyright (c) 2022 Dzmitry Sankouski (dsankouski@gmail.com)
+ */
+#include <init.h>
+#include <env.h>
+#include <fdtdec.h>
+#include <fdt_support.h>
+#include <fdt.h>
+#include <common.h>
+#include <linux/errno.h>
+#include <asm/system.h>
+#include <asm/armv8/mmu.h>
+
+static ulong reg0 __section(".data");
+
+/**
+ * Save x0 register value, assuming previous bootloader set it to
+ * point on loaded fdt or (for older linux kernels)atags.
+ */
+void save_boot_params(ulong r0)
+{
+       reg0 = r0;
+       save_boot_params_ret();
+}
+
+bool is_addr_accessible(phys_addr_t addr)
+{
+       struct mm_region *mem = mem_map;
+       phys_addr_t bank_start;
+       phys_addr_t bank_end;
+
+       while (mem->size) {
+               bank_start = mem->phys;
+               bank_end = bank_start + mem->size;
+               debug("check if block %pap - %pap includes %pap\n", &bank_start, &bank_end, &addr);
+               if (addr > bank_start && addr < bank_end)
+                       return true;
+               mem++;
+       }
+
+       return false;
+}
+
+int save_prev_bl_data(void)
+{
+       struct fdt_header *fdt_blob;
+       int node;
+       u64 initrd_start_prop;
+
+       if (!is_addr_accessible((phys_addr_t)reg0))
+               return -ENODATA;
+
+       fdt_blob = (struct fdt_header *)reg0;
+       if (!fdt_valid(&fdt_blob)) {
+               pr_warn("%s: address 0x%lx is not a valid fdt\n", __func__, reg0);
+               return -ENODATA;
+       }
+
+       if (CONFIG_IS_ENABLED(SAVE_PREV_BL_FDT_ADDR))
+               env_set_addr("prevbl_fdt_addr", (void *)reg0);
+       if (!CONFIG_IS_ENABLED(SAVE_PREV_BL_INITRAMFS_START_ADDR))
+               return 0;
+
+       node = fdt_path_offset(fdt_blob, "/chosen");
+       if (!node) {
+               pr_warn("%s: chosen node not found in device tree at addr: 0x%lx\n",
+                                       __func__, reg0);
+               return -ENODATA;
+       }
+       /*
+        * linux,initrd-start property might be either 64 or 32 bit,
+        * depending on primary bootloader implementation.
+        */
+       initrd_start_prop = fdtdec_get_uint64(fdt_blob, node, "linux,initrd-start", 0);
+       if (!initrd_start_prop) {
+               debug("%s: attempt to get uint64 linux,initrd-start property failed, trying uint\n",
+                               __func__);
+               initrd_start_prop = fdtdec_get_uint(fdt_blob, node, "linux,initrd-start", 0);
+               if (!initrd_start_prop) {
+                       debug("%s: attempt to get uint failed, too\n", __func__);
+                       return -ENODATA;
+               }
+       }
+       env_set_addr("prevbl_initrd_start_addr", (void *)initrd_start_prop);
+
+       return 0;
+}
index 394b26f246a15d75ffb6d7fd9e0469c839c36057..ec5b956490de451e60a2538266cedeec6cdab3d4 100644 (file)
@@ -1192,4 +1192,28 @@ config DEFAULT_FDT_FILE
        help
          This option is used to set the default fdt file to boot OS.
 
+config SAVE_PREV_BL_FDT_ADDR
+       depends on ARM
+       bool "Saves fdt address, passed by the previous bootloader, to env var"
+       help
+         When u-boot is used as a chain-loaded bootloader (replacing OS kernel),
+         enable this option to save fdt address, passed by the
+         previous bootloader for future use.
+         Address is saved to `prevbl_fdt_addr` environment variable.
+
+         If no fdt was provided by previous bootloader, no env variables
+         will be created.
+
+config SAVE_PREV_BL_INITRAMFS_START_ADDR
+       depends on ARM
+       bool "Saves initramfs address, passed by the previous bootloader, to env var"
+       help
+         When u-boot is used as a chain-loaded bootloader(replacing OS kernel),
+         enable this option to save initramfs address, passed by the
+         previous bootloader for future use.
+         Address is saved to `prevbl_initrd_start_addr` environment variable.
+
+         If no initramfs was provided by previous bootloader, no env variables
+         will be created.
+
 endmenu                # Booting
index b92c1bb0be1b5ff3fd168574ee6bbb5e1078b744..8dc87ed2be4cad4ef63219a8b73cf3a7627e4141 100644 (file)
@@ -445,6 +445,11 @@ static int initr_env(void)
                env_set_hex("fdtcontroladdr",
                            (unsigned long)map_to_sysmem(gd->fdt_blob));
 
+       #if (CONFIG_IS_ENABLED(SAVE_PREV_BL_INITRAMFS_START_ADDR) || \
+                                               CONFIG_IS_ENABLED(SAVE_PREV_BL_FDT_ADDR))
+               save_prev_bl_data();
+       #endif
+
        /* Initialize from environment */
        image_load_addr = env_get_ulong("loadaddr", 16, image_load_addr);
 
index 74496500d290338fded55c99e485134d847ef345..7b8f62c1218fc3166d89e79cb1cf55b3fb3343de 100644 (file)
@@ -155,6 +155,19 @@ int arch_setup_bdinfo(void);
  */
 int setup_bdinfo(void);
 
+#if defined(CONFIG_SAVE_PREV_BL_INITRAMFS_START_ADDR) || \
+defined(CONFIG_SAVE_PREV_BL_FDT_ADDR)
+/**
+ * save_prev_bl_data - Save prev bl data in env vars.
+ *
+ * When u-boot is chain-loaded, save previous bootloader data,
+ * like initramfs address to environment variables.
+ *
+ * Return: 0 if ok; -ENODATA on error
+ */
+int save_prev_bl_data(void);
+#endif
+
 /**
  * cpu_secondary_init_r() - CPU-specific secondary initialization
  *