]> git.ipfire.org Git - thirdparty/openwrt.git/commitdiff
realtek: rt-loader: add ROM uImage lookup (aka standalone) 19832/head
authorMarkus Stockhausen <markus.stockhausen@gmx.de>
Fri, 22 Aug 2025 08:43:23 +0000 (04:43 -0400)
committerHauke Mehrtens <hauke@hauke-m.de>
Wed, 3 Sep 2025 19:36:34 +0000 (21:36 +0200)
The rt-loader currently only supports booting piggy backed lzma
compressed kernels. This requires a data layout where the kernel
directly follows the loader. That might not be sufficient for
more complex flash layouts.

Especially bootbase devices (like ZyXEL GS1920) will need some
kind of chain loading that needs to be explored yet.

Enhance the rt-loader as follows:

- Allow to build as standalone version
- In this case a flash start address is given
- During boot loader will search the ROM starting from that address
- If it finds a uImage this will be loaded into RAM
- Afterwards it will be decompressed to its load address
- While we are here add uncompressed uImage support

As always the implementation tries to be as simple as possible.

- uImage detection works without magics
- uImage will be loaded to highest possible memory address
- Documentation in Makefile has been adapted accordingly

Funny side fact: A standalone rt-loader can chain load a piggy
backed rt-loader from flash.

During bootup loader will show

rt-loader
Running on RTL8380M (chip id 6275C) with 256MB
Relocate 15760 bytes from 0x82000000 to 0x8ffa0000
Searching for uImage starting at 0xb45a0000 ...
uImage 'MIPS OpenWrt Linux-6.12.40' found at 0xb45a0000 with load address 0x80100000
Copy 2923034 bytes of image data to 0x8fcd61e6 ...
Extract image with 2923034 bytes from 0x8fcd61e6 to 0x80100000 ...
Final kernel size is 2923034 bytes
Booting kernel from 0x80100000 ...

Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Link: https://github.com/openwrt/openwrt/pull/19832
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
target/linux/realtek/image/rt-loader/Makefile
target/linux/realtek/image/rt-loader/include/globals.h
target/linux/realtek/image/rt-loader/src/main.c
target/linux/realtek/image/rt-loader/src/startup.S

index 6479db705c8448a4070625f01ac4508d8b3d7cd1..ade61697bf4f0b23954e3a03dc167128f657594b 100644 (file)
@@ -5,45 +5,60 @@
 # avoid copying files around where possible. Therefore it is controlled by the following
 # input parameters
 #
-# KERNEL_IMG_IN:       The filename of an LZMA compressed kernel image. This is required
+# KERNEL_IMG_IN:       The filename of an LZMA compressed kernel. If given the loader
+#                      and the kernel will be concatenated (piggy back loading).
+# FLASH_ADDR:          The kernel address in the ROM. If given, the loeader will be
+#                      created standalone and search a LZMA compressed uImage on the
+#                      target device starting from this address.
 # KERNEL_IMG_OUT:      The filename of the kernel image with the rt-loader prepended.
 #                      If not given it will be created as image.bin into the BUILD_DIR.
 # BUILD_DIR:           The temporary build dir. If not given it will be set to "build".
 #
-# To add it into the OpenWrt toolchain just create two new build commands
+# To add it into the OpenWrt toolchain just create the following new build commands
 #
-# define Build/rt-loader
+# define Build/rt-compress
+#   $(STAGING_DIR_HOST)/bin/xz --format=lzma -9 --stdout "$@" > "$@.new"
+#   mv "$@.new" "$@"
+# endef
+#
+# define Build/rt-loader-piggy-back
 #   $(MAKE) all clean -C rt-loader CROSS_COMPILE="$(TARGET_CROSS)" \
 #          KERNEL_IMG_IN="$@" KERNEL_IMG_OUT="$@.new" BUILD_DIR="$@.build"
 #   mv "$@.new" "$@"
 # endef
 #
-# define Build/rt-compress
-#   $(STAGING_DIR_HOST)/bin/xz --format=lzma -9 --stdout "$@" > "$@.new"
+# define Build/rt-loader-standalone
+#   $(MAKE) all clean -C rt-loader CROSS_COMPILE="$(TARGET_CROSS)" \
+#          FLASH_ADDR=$(FLASH_ADDR) KERNEL_IMG_OUT="$@.new" BUILD_DIR="$@.build"
 #   mv "$@.new" "$@"
 # endef
 #
-# Use them in a new kernel build recipe
+# Finally use them in new kernel build recipes. E.g. as all-in-one self extracting uImage.
 #
 # define Device/uimage-rt-loader
-#   KERNEL/rt-loader := kernel-bin | append-dtb | rt-compress | rt-loader
+#   KERNEL/rt-loader := kernel-bin | append-dtb | rt-compress | rt-loader-piggy-back
 #   KERNEL := $$(KERNEL/rt-loader) | uImage none
 #   KERNEL_INITRAMFS := $$(KERNEL/rt-loader) | uImage none
 # endef
 #
-# And finally add it to the target device. E.g.
+# Or as direct startable standlone version plus a kernel uImage
 #
-# define Device/linksys_lgs310c
-#   $(Device/uimage-rt-loader)
-#   ...
+# define Device/rt-loader-uimage
+#   FLASH_ADDR := 0xb4000000
+#   KERNEL/rt-loader := rt-loader-standalone
+#   KERNEL := kernel-bin | append-dtb | rt-compress | uImage lzma
 # endef
 
+FLASH_ADDR_NONE        := 0x0
+FLASH_ADDR     ?= $(FLASH_ADDR_NONE)
+
 CC             := $(CROSS_COMPILE)gcc
 LD             := $(CROSS_COMPILE)ld
 OBJCOPY                := $(CROSS_COMPILE)objcopy
 OBJDUMP                := $(CROSS_COMPILE)objdump
 
 CFLAGS         = -fpic -mabicalls -O2 -fno-builtin-printf -Iinclude
+CFLAGS         += -DFLASH_ADDR=$(FLASH_ADDR)
 
 ASFLAGS                = -fpic -msoft-float -Iinclude
 
@@ -58,6 +73,8 @@ BUILD_DIR     ?= build
 IMAGE_OBJ      := $(BUILD_DIR)/image.o
 IMAGE_ELF      := $(BUILD_DIR)/image.elf
 
+KERNEL_IMG_NONE        := $(BUILD_DIR)/empty_kernel.bin
+KERNEL_IMG_IN  ?= $(KERNEL_IMG_NONE)
 KERNEL_IMG_OUT ?= $(BUILD_DIR)/image.bin
 
 OBJECTS_C      = $(filter %.c,$(SOURCES))
@@ -67,9 +84,13 @@ OBJECTS              := $(OBJECTS_S:.S=.o) $(OBJECTS_C:.c=.o)
 OBJECTS                := $(patsubst %.o, $(BUILD_DIR)/%.o, $(OBJECTS)) $(IMAGE_OBJ)
 
 ifneq ($(MAKECMDGOALS),clean)
-ifndef KERNEL_IMG_IN
-$(error Compressed kernel image not given via KERNEL_IMG_IN)
-endif
+  ifeq ($(KERNEL_IMG_IN)$(FLASH_ADDR),$(KERNEL_IMG_NONE)$(FLASH_ADDR_NONE))
+    $(error Set either KERNEL_IMG_IN or FLASH_ADDR)
+  else ifneq ($(FLASH_ADDR),$(FLASH_ADDR_NONE))
+    $(info Create standalone rt-loader, loading uimage from address $(FLASH_ADDR))
+  else
+    $(info Create piggy backed rt-loader, loading appended kernel binary "$(KERNEL_IMG_IN)")
+  endif
 endif
 
 all: $(KERNEL_IMG_OUT)
@@ -93,6 +114,10 @@ $(IMAGE_ELF): $(OBJECTS)
 $(KERNEL_IMG_OUT): $(IMAGE_ELF)
        $(OBJCOPY) -O binary $< $@
 
+$(KERNEL_IMG_IN):
+       @mkdir -p $(dir $@)
+       @echo "DUMMY-KERNEL-IMAGE" > $@
+
 clean:
        rm -rf $(BUILD_DIR)/
 
index 5ed65ba67093a71014c36590903262cd79cfab5e..a8e970d7d45268379fa69f627b2e3e2d4e10c5bb 100644 (file)
@@ -6,6 +6,11 @@
 #ifndef _GLOBALS_H_
 #define _GLOBALS_H_
 
+#define UIMAGE_HDR_SIZE                64
+#define UIMAGE_OS_LINUX                5
+#define UIMAGE_COMP_NONE       0
+#define UIMAGE_COMP_LZMA       3
+
 #define KSEG0                  0x80000000
 #define STACK_SIZE             0x10000
 #define HEAP_SIZE              0x40000
index 0a53fcef7dd28310eb7a9356019bf197140a96ea..acdd782ffdecd8e9428254d25eeaea217dbf1a8f 100644 (file)
@@ -10,6 +10,7 @@
  * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org>
  */
 
+#include <stdbool.h>
 #include "board.h"
 #include "globals.h"
 #include "memory.h"
 extern void *_kernel_load_addr;
 extern void *_kernel_data_addr;
 extern int _kernel_data_size;
+extern int _kernel_comp_type;
 extern void *_my_load_addr;
 extern int _my_load_size;
+extern int _my_run_count;
 
-extern int unlzma(unsigned char *buf, long in_len,
-          long (*fill)(void*, unsigned long),
-          long (*flush)(void*, unsigned long),
-          unsigned char *output,
-          long *outlen,
-          long *posp,
-          void(*error)(char *x));
+extern int unlzma(unsigned char *buf, long in_len, long (*fill)(void*, unsigned long),
+          long (*flush)(void*, unsigned long), unsigned char *output,
+          long *outlen, long *posp, void(*error)(char *x));
 
 typedef void (*entry_func_t)(unsigned long reg_a0, unsigned long reg_a1,
                             unsigned long reg_a2, unsigned long reg_a3);
 
+
+static bool is_uimage(void *m)
+{
+       unsigned int data[UIMAGE_HDR_SIZE / sizeof(int)];
+       unsigned int image_crc;
+
+       memcpy(data, m, UIMAGE_HDR_SIZE);
+       image_crc = data[1];
+       data[1] = 0;
+
+       return image_crc == crc32(data, UIMAGE_HDR_SIZE);
+}
+
 void *relocate(void *src, int len)
 {
        void *addr;
        unsigned int offs;
 
        /*
-        * Relocate to highest possible memory address. This is usually the RAM size minus some
-        * space for the heap and the stack pointer. As we do not have any highmem features
-        * limit this to 256MB.
+        * Relocate to highest possible memory address. This is usually the RAM size
+        * minus some space for the heap and the stack pointer. As we do not have any
+        * highmem features limit this to 256MB.
         */
-
        offs = (board_get_memory() - STACK_SIZE - HEAP_SIZE - len - 1024) & 0xfff0000;
        addr = (void *)KSEG0 + offs;
 
@@ -81,43 +92,109 @@ void decompress_error(char *x)
 void *decompress(void *out, void *in, int len)
 {
        long outlen;
+       int ret = 1;
+
+       printf("Extract image with %d bytes from 0x%08x to 0x%08x ...\n", len, in, out);
+
+       switch (_kernel_comp_type) {
+       case UIMAGE_COMP_LZMA:
+               ret = unlzma(in, len, 0, 0, out, &outlen, 0, decompress_error);
+               break;
+       case UIMAGE_COMP_NONE:
+               memcpy(out, in, len);
+               outlen = len;
+               ret = 0;
+               break;
+       default:
+               printf("Unknown uImage compression type %d\n", _kernel_comp_type);
+               break;
+       }
 
-       printf("Extract kernel with %d bytes from 0x%08x to 0x%08x ...\n", len, in, out);
-
-       if (unlzma(in, len, 0, 0, out, &outlen, 0, decompress_error))
+       if (ret)
                board_panic();
 
-       printf("Extracted kernel size is %d bytes\n", outlen);
+       printf("Final kernel size is %d bytes\n", outlen);
        flush_cache(out, outlen);
 
        return out;
 }
 
+void search_image(void **flash_addr, int *flash_size, void **load_addr)
+{
+       unsigned char *addr = *flash_addr;
+       unsigned int image_size = 0;
+       unsigned int *maxaddr;
+
+       printf("Searching for uImage starting at 0x%08x ...\n", addr);
+
+       /*
+        * The most basic way to find a uImage is to lookup the operating system
+        * opcode (for Linux it is 5). Then verify the header checksum. This is
+        * reasonably fast and all other magic value or constants can be avoided.
+        */
+       *flash_addr = NULL;
+       for (int i = 0; i < 256 * 1024; i += 4, addr += 4) {
+               if ((addr[28] == UIMAGE_OS_LINUX) && is_uimage(addr)) {
+                       *flash_addr = addr;
+                       *flash_size = *(int *)(addr + 12);
+                       *load_addr = *(void **)(addr + 16);
+                       _kernel_comp_type = addr[31];
+                       break;
+               }
+       }
+}
+
+void load_kernel(void *flash_start)
+{
+       void *flash_addr = flash_start;
+
+       search_image(&flash_addr, &_kernel_data_size, &_kernel_load_addr);
+       _kernel_data_addr = _my_load_addr - _kernel_data_size - 1024;
+
+       if (!flash_addr) {
+               printf("Kernel uImage not found\n");
+               board_panic();
+       }
+
+       printf("uImage '%s' found at 0x%08x with load address 0x%08x\n",
+              (char *)(flash_addr + 32), flash_addr, _kernel_load_addr);
+       printf("Copy %d bytes of image data to 0x%08x ...\n",
+              _kernel_data_size, _kernel_data_addr);
+
+       memcpy(_kernel_data_addr, flash_addr + UIMAGE_HDR_SIZE, _kernel_data_size);
+}
+
 void main(unsigned long reg_a0, unsigned long reg_a1,
          unsigned long reg_a2, unsigned long reg_a3)
 {
+       void *flash_start = (void *)FLASH_ADDR; /* from makefile */
        entry_func_t fn;
 
-       if (_kernel_load_addr == _my_load_addr) {
-               /*
-                * During first run relocate the whole package to the end of memory. Use
-                * _my_load_size as relocation length. That includes the bss section, aka
-                * uninitialized globals. So it is possible to initialize globals during
-                * first run and have them at hand after relocation.
-                */
-
+       /*
+        * During first run relocate the whole package to the end of memory. Use
+        * _my_load_size as relocation length. That includes the bss section, aka
+        * uninitialized globals. So it is possible to initialize globals during
+        * first run and have them at hand after relocation.
+        */
+       if (_my_run_count == 1) {
                welcome();
                fn = relocate(_my_load_addr, _my_load_size);
                fn(reg_a0, reg_a1, reg_a2, reg_a3);
-       } else {
-               /*
-                * During second run extract the attached kernel image to the memory address
-                * that the loader was loaded to in the first run.
-                */
+       }
 
-               fn = decompress(_kernel_load_addr, _kernel_data_addr, _kernel_data_size);
+       /*
+        * Check if we have been started standalone. So no piggy backed kernel.
+        * Search flash for kernel uImage and copy it to memory before the loader.
+        */
+       if (flash_start)
+               load_kernel(flash_start);
 
-               printf("Booting kernel from 0x%08x ...\n\n", fn);
-               fn(reg_a0, reg_a1, reg_a2, reg_a3);
-       }
+       /*
+        * Finally extract the attached kernel image to the load address. This is
+        * either the first load address or what was found in uImage on flash
+        */
+       fn = decompress(_kernel_load_addr, _kernel_data_addr, _kernel_data_size);
+
+       printf("Booting kernel from 0x%08x ...\n\n", fn);
+       fn(reg_a0, reg_a1, reg_a2, reg_a3);
 }
index 60eb1e480ee35ceca9987d21b6f85a6ebe77f5a2..5d2f99338c432ad808dfee1cdbd58de97e8023f4 100644 (file)
@@ -4,9 +4,9 @@
 #include "globals.h"
 
 # This start code allows to run a position independent code (PIC) on bare metal. In that case
-# all addresses are looked up via the global offset table (GOT). But that must be filled during
-# this initialization sequence. Without a proper GOT using standard "la" instruction in the code
-# will not work. Provide a macro that avoids the dependency.
+# all addresses are looked up via the global offset table (GOT). That must be filled during
+# this initialization sequence. Without a proper GOT using standard "la" instruction in the
+# code will not work. Provide a macro that avoids the dependency.
 
 .macro _LA reg, symbol
        lui \reg, %hi(\symbol)
@@ -48,15 +48,18 @@ _where_am_i:
        mtc0    $zero, CP0_COMPARE
        _EHB
 
-# Check if this our first run (_kernel_load_addr = 0?)
+# Increase run counter and check if this is the first run
 
-       _LA     $t6, _kernel_load_addr
+       _LA     $t6, _my_run_count
        lw      $t7, 0($t6)
+       addi    $t8, $t7, 1
+       sw      $t8, 0($t6)
        bne     $zero, $t7, _init_done
        nop
 
 # During first run store the current load address as the target kernel load address.
 
+       _LA     $t6, _kernel_load_addr
        sw      $t9, 0($t6)
 
 # Same for the global variables in the BSS section. Clear them only during the first run. This
@@ -179,6 +182,10 @@ _heap_addr:
        .globl _heap_addr_max
 _heap_addr_max:
        .word 0
+# number of loads
+       .globl _my_run_count
+_my_run_count:
+       .word 0
 # current program load address
        .globl _my_load_addr
 _my_load_addr:
@@ -199,3 +206,7 @@ _kernel_data_addr:
        .globl _kernel_data_size
 _kernel_data_size:
        .word 0
+# compression method of attached kernel (usually LZMA)
+       .globl _kernel_comp_type
+_kernel_comp_type:
+       .word UIMAGE_COMP_LZMA