From: Ahmad Fatoum <a.fatoum@barebox.org>
To: barebox@lists.infradead.org
Cc: mfe@pengutronix.de, mtr@pengutronix.de,
Ahmad Fatoum <a.fatoum@barebox.org>
Subject: [RFC PATCH 05/17] PBL: introduce board info based entry point framework
Date: Tue, 27 Oct 2026 08:24:38 +0100 [thread overview]
Message-ID: <20261027074014.3280396-6-a.fatoum@barebox.org> (raw)
In-Reply-To: <20261027074014.3280396-1-a.fatoum@barebox.org>
Every board open-codes its PBL entry point: set up the stack, relocate,
call setup_c(), do the lowlevel init and so on, all in code that runs
before the C environment is ready. This is easy to get wrong and leaves
common code with no idea of what the boot flow looks like.
Let's have boards describe it instead: struct pbl_board_info has up to
three stages (ROM, OCRAM, DRAM), each with a memory region, a stack, an
optional relocation address and callbacks for CPU, SoC and board init
and for chainloading. ENTRY_POINT() defines it with the image head and
defaults of the given SoC, which the board may override, hence the
__diag_ignore_all() imported from Linux.
The image head branches to pbl_head_start_v1(), which determines the
stage from the PC, sets up its stack and runs the CPU init callback,
before relocating, clearing BSS and running the remaining callbacks.
As this runs before there is a stack, which C can't guarantee,
architectures are meant to implement pbl_head_start_v1() in assembly
and select ARCH_HAS_PBL_HEAD_START_ASM. The C implementation added here
is weak and serves as reference. With COMPILE_TEST, PBL_HEAD_START_ASM
can be disabled to test it. It's naked where the compiler allows C in
naked functions, which GCC does for ARM32 and RISC-V, but clang doesn't.
Everything is versioned, so we can add a v2 alongside should the scheme
ever need to change incompatibly.
Assisted-by: Claude:fable-5.1
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
arch/Kconfig | 28 ++++++
include/linux/compiler-clang.h | 21 +++++
include/linux/compiler-gcc.h | 3 +
include/linux/compiler_types.h | 10 ++
include/pbl/entry.h | 158 +++++++++++++++++++++++++++++++
pbl/Makefile | 1 +
pbl/entry.c | 165 +++++++++++++++++++++++++++++++++
7 files changed, 386 insertions(+)
create mode 100644 include/pbl/entry.h
create mode 100644 pbl/entry.c
diff --git a/arch/Kconfig b/arch/Kconfig
index d24cf525b029..f36eab86d891 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -95,6 +95,34 @@ config ARCH_HAS_DATA_ABORT_MASK_PBL
config ARCH_HAS_ZERO_PAGE
bool
+config ARCH_HAS_NAKED
+ def_bool y
+ depends on (ARM && CPU_32) || RISCV
+ # clang allows only basic asm statements in naked functions
+ depends on !CC_IS_CLANG
+ help
+ The compiler supports C in naked functions: it emits neither
+ prologue nor epilogue, but may still spill to the stack.
+
+config ARCH_HAS_PBL_ENTRY
+ bool
+ help
+ The architecture has PBL image heads for ENTRY_POINT() boards.
+
+config ARCH_HAS_PBL_HEAD_START_ASM
+ bool
+ help
+ The architecture implements pbl_head_start_v1() in assembly.
+
+config PBL_HEAD_START_ASM
+ bool "Use the assembly PBL stage dispatch" if COMPILE_TEST
+ depends on ARCH_HAS_PBL_HEAD_START_ASM
+ default y
+ help
+ Say N to use the C pbl_head_start_v1() in pbl/entry.c instead,
+ which may spill to the stack before it is set up. Only useful
+ for testing.
+
config ARCH_HAS_MALLOC_SIZE
bool
help
diff --git a/include/linux/compiler-clang.h b/include/linux/compiler-clang.h
index 681e347283b2..4b83885a9255 100644
--- a/include/linux/compiler-clang.h
+++ b/include/linux/compiler-clang.h
@@ -55,3 +55,24 @@
#define __must_be_array(a) BUILD_BUG_ON_ZERO(__same_type((a), &(a)[0]))
#define __assume_aligned(a, ...) \
__attribute__((__assume_aligned__(a, ## __VA_ARGS__)))
+
+/*
+ * Turn individual warnings and errors on and off locally, depending
+ * on version.
+ */
+#define __diag_str1(s) #s
+#define __diag_str(s) __diag_str1(s)
+#define __diag(s) _Pragma(__diag_str(clang diagnostic s))
+
+#define __diag_clang(version, severity, s) \
+ __diag_clang_ ## version(__diag_clang_ ## severity s)
+
+/* Severity used in pragma directives */
+#define __diag_clang_ignore ignored
+#define __diag_clang_warn warning
+#define __diag_clang_error error
+
+#define __diag_clang_13(s) __diag(s)
+
+#define __diag_ignore_all(option, comment) \
+ __diag_clang(13, ignore, option)
diff --git a/include/linux/compiler-gcc.h b/include/linux/compiler-gcc.h
index 2534386d040f..79d23ba62072 100644
--- a/include/linux/compiler-gcc.h
+++ b/include/linux/compiler-gcc.h
@@ -235,6 +235,9 @@
#define __diag_GCC_8(s)
#endif
+#define __diag_ignore_all(option, comment) \
+ __diag(__diag_GCC_ignore option)
+
/*
* Prior to 9.1, -Wno-alloc-size-larger-than (and therefore the "alloc_size"
* attribute) do not work, and must be disabled.
diff --git a/include/linux/compiler_types.h b/include/linux/compiler_types.h
index 0e49c6795409..7837f45efca2 100644
--- a/include/linux/compiler_types.h
+++ b/include/linux/compiler_types.h
@@ -313,6 +313,10 @@ struct ftrace_likely_data {
#define __diag_GCC(version, severity, string)
#endif
+#ifndef __diag_ignore_all
+#define __diag_ignore_all(option, comment)
+#endif
+
#define __diag_push() __diag(push)
#define __diag_pop() __diag(pop)
@@ -452,4 +456,10 @@ struct ftrace_likely_data {
#define __prereloc \
notrace __no_sanitize_address __no_stack_protector
+/*
+ * Marks functions that may run before the stack is set up and thus must
+ * not spill. Documentation only, nothing enforces it.
+ */
+#define __unsafe_prestack
+
#endif /* __LINUX_COMPILER_TYPES_H */
diff --git a/include/pbl/entry.h b/include/pbl/entry.h
new file mode 100644
index 000000000000..17056a3ab1ca
--- /dev/null
+++ b/include/pbl/entry.h
@@ -0,0 +1,158 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* SPDX-FileCopyrightText: 2026 Ahmad Fatoum */
+
+#ifndef __PBL_ENTRY_H_
+#define __PBL_ENTRY_H_
+
+#include <linux/bits.h>
+#include <linux/compiler.h>
+#include <linux/types.h>
+#include <asm-generic/memory_layout.h>
+#include <pbl/soc-type.h>
+
+/* Special stacktop values, a real stack top is 16-byte aligned */
+#define PBL_STACKTOP_KEEP 0 /* leave SP as it is */
+#define PBL_STACKTOP_BEFORE_BAREBOX 1 /* grows down from the image start */
+#define PBL_STACKTOP_AFTER_BAREBOX 2 /* CONFIG_STACK_SIZE behind the image end */
+
+enum pbl_stage_idx {
+ PBL_STAGE_ROM, /* pre-sram */
+ PBL_STAGE_OCRAM, /* pre-ram */
+ PBL_STAGE_DRAM, /* some ram */
+ PBL_STAGE_FINAL, /* all ram */
+ PBL_STAGE_COUNT, /* sentinel */
+};
+
+typedef void (*pbl_board_fn_t)(void);
+typedef enum pbl_stage_idx (*pbl_locate_pc_fn_t)(void);
+
+/*
+ * Only the callbacks of the stage the PBL was entered in are run.
+ *
+ * prereloc_cpu_init runs on the stage's stack, but unrelocated and before
+ * BSS is cleared, so it must be __prereloc and may not use globals.
+ * The other callbacks run relocated with BSS cleared.
+ */
+struct pbl_stage {
+ unsigned long stacktop; /* stack top or PBL_STACKTOP_* */
+ unsigned long relocaddr; /* 0: don't relocate to specific addr */
+
+ /*
+ * Memory the stage runs from: barebox proper starts out with
+ * mem_minsz, mem_maxsz (default: mem_minsz) is only used to find
+ * the stage by PC.
+ */
+ unsigned long mem_start;
+ unsigned long mem_minsz;
+ unsigned long mem_maxsz;
+
+ void *board_data;
+
+ pbl_board_fn_t prereloc_cpu_init;
+ pbl_board_fn_t soc_init;
+ void (*board_init)(unsigned long entry_data);
+ void (*soc_chainload)(void *board_data);
+};
+
+struct pbl_board_info {
+ unsigned int soc_type; /* see pbl_set_soc_type() */
+
+ /* Called without a stack, so it must not spill */
+ pbl_locate_pc_fn_t assembly_locate_pc;
+
+ struct pbl_stage rom_stage; /* PBL_STAGE_ROM */
+ struct pbl_stage first_stage; /* PBL_STAGE_OCRAM */
+ struct pbl_stage second_stage; /* PBL_STAGE_DRAM */
+};
+
+extern struct pbl_board_info __pbl_board_info;
+
+/* Always inlined, because it's called before relocation */
+static __always_inline struct pbl_stage *
+pbl_stage(struct pbl_board_info *pbi, int idx)
+{
+ switch (idx) {
+ case PBL_STAGE_ROM:
+ return &pbi->rom_stage;
+ case PBL_STAGE_OCRAM:
+ return &pbi->first_stage;
+ case PBL_STAGE_DRAM:
+ return &pbi->second_stage;
+ default:
+ return NULL;
+ }
+}
+
+struct pbl_stage *pbl_get_stage(void);
+
+void setup_c(void);
+
+#ifdef CONFIG_ARCH_HAS_NAKED
+#define __pbl_naked __naked
+#else
+#define __pbl_naked
+#endif
+
+/*
+ * pbl_head_start_v1 - stage dispatch the image head branches to
+ *
+ * Runs unrelocated and without a stack: determines the stage, switches
+ * to its stack, runs prereloc_cpu_init and continues in
+ * pbl_head_reloc_v1(). entry_data is a boot register the head passes on
+ * to board_init.
+ *
+ * The C implementation in pbl/entry.c is weak, so the architecture can
+ * override it with one in assembly, see CONFIG_PBL_HEAD_START_ASM.
+ */
+void __noreturn pbl_head_start_v1(unsigned long entry_data);
+
+/* Relocates, clears BSS and runs the remaining callbacks. Needs a stack */
+void __noreturn pbl_head_reloc_v1(unsigned long stage,
+ unsigned long entry_data);
+
+/*
+ * ENTRY_POINT_HEAD - define entry point @name with image head @head
+ *
+ * Opens the board info, ENTRY_POINT_END closes it. The function only
+ * keeps head and board info from being garbage collected, but ARM's
+ * ENTRY_FUNCTION() will branch to it, so it may not use the stack.
+ */
+#define ENTRY_POINT_HEAD(name, head) \
+ void name(ulong r0, ulong r1, ulong r2); \
+ void __pbl_naked __unsafe_prestack \
+ __section(.text_head_entry_##name) name \
+ (ulong r0, ulong r1, ulong r2) \
+ { \
+ __keep_symbolref(head); \
+ __diag_push(); \
+ __diag_ignore_all("-Woverride-init", \
+ "Allow board overrides for SoC defaults"); \
+ static __section(.pbl_board_info_##name) \
+ struct pbl_board_info __board_info = { \
+ /* unset fields are zero (static) */
+
+/* Closes the board info, but not the function, for ARM's ENTRY_FUNCTION() */
+#define ENTRY_POINT_CONTINUE \
+ }; \
+ __diag_pop(); \
+ __keep_symbolref(__board_info);
+
+#define ENTRY_POINT_END \
+ ENTRY_POINT_CONTINUE \
+ }
+
+#define PBL_SOC_PASTE2(soc, field) PBL_SOC_##soc##_##field
+#define PBL_SOC_PASTE(soc, field) PBL_SOC_PASTE2(soc, field)
+
+/*
+ * ENTRY_POINT - define entry point @name for a board with SoC @soc
+ *
+ * Head and board info defaults come from PBL_SOC_<soc>_HEAD and
+ * PBL_SOC_<soc>_DEFAULTS. The board info fields that follow override
+ * the defaults.
+ */
+#define ENTRY_POINT(soc, name) \
+ ENTRY_POINT_HEAD(name, PBL_SOC_PASTE(soc, HEAD))\
+ PBL_SOC_PASTE(soc, DEFAULTS),
+
+#endif
diff --git a/pbl/Makefile b/pbl/Makefile
index 5d7c9b06015d..3255feb9d645 100644
--- a/pbl/Makefile
+++ b/pbl/Makefile
@@ -4,6 +4,7 @@
# only unsed by the pbl
#
pbl-$(CONFIG_PBL_CONSTRUCTORS) += ctors.o
+pbl-$(CONFIG_ARCH_HAS_PBL_ENTRY) += entry.o
pbl-y += misc.o
pbl-y += string.o
pbl-y += malloc.o
diff --git a/pbl/entry.c b/pbl/entry.c
new file mode 100644
index 000000000000..1349e7c56a93
--- /dev/null
+++ b/pbl/entry.c
@@ -0,0 +1,165 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2026 Ahmad Fatoum
+/*
+ * Stage dispatch for ENTRY_POINT() boards. pbl_head_start_v1() runs
+ * before there is a stack, but even naked, the compiler may spill, so
+ * the C version here is weak for architectures to override in assembly.
+ */
+
+#include <linux/types.h>
+#include <linux/align.h>
+#include <asm/reloc.h>
+#include <asm/sections.h>
+#include <asm-generic/registers.h>
+#include <barebox.h>
+#include <pbl/entry.h>
+#include <pbl/soc-type.h>
+#include <pbl/handoff-data.h>
+
+/*
+ * runtime_address() without the volatile, which would cost a stack slot.
+ * It's only needed across relocation, which doesn't happen this early.
+ */
+#define prestack_runtime_address(sym) \
+ ((void *)RELOC_HIDE((char *)(sym), global_variable_offset()))
+
+static struct pbl_stage *current_stage;
+
+struct pbl_stage *pbl_get_stage(void)
+{
+ return current_stage;
+}
+
+static __always_inline __prereloc __unsafe_prestack bool
+stage_has_entry(const struct pbl_stage *stage)
+{
+ return stage->soc_init || stage->board_init || stage->soc_chainload;
+}
+
+static __always_inline __prereloc __unsafe_prestack int
+pbl_determine_stage_idx(struct pbl_board_info *pbi)
+{
+ int earliest_stage_idx = -1;
+ int stage_idx;
+
+ if (pbi->assembly_locate_pc) {
+ pbl_locate_pc_fn_t locate_pc;
+
+ locate_pc = (void *)((ulong)pbi->assembly_locate_pc +
+ get_runtime_offset());
+ return locate_pc();
+ }
+
+ for (stage_idx = 0; stage_idx < PBL_STAGE_COUNT; stage_idx++) {
+ struct pbl_stage *stage = pbl_stage(pbi, stage_idx);
+
+ if (!stage)
+ continue;
+
+ if (stage_has_entry(stage) && earliest_stage_idx < 0)
+ earliest_stage_idx = stage_idx;
+
+ if (pc_inside(stage->mem_start,
+ stage->mem_maxsz ?: stage->mem_minsz))
+ return stage_idx;
+ }
+
+ return earliest_stage_idx;
+}
+
+static void __noreturn pbl_head_cpu_init_v1(ulong stage_ul, ulong entry_data);
+static void __noreturn pbl_head_entry_v1(ulong entry_data);
+
+void __weak __pbl_naked __prereloc __unsafe_prestack
+pbl_head_start_v1(unsigned long entry_data)
+{
+ struct pbl_board_info *pbi =
+ prestack_runtime_address(&__pbl_board_info);
+ struct pbl_stage *stage;
+ ulong stage_ul;
+ ulong stacktop;
+
+ stage = pbl_stage(pbi, pbl_determine_stage_idx(pbi));
+ if (!stage)
+ __hang();
+
+ stage_ul = (ulong)stage;
+ stacktop = stage->stacktop;
+
+ if (stacktop == PBL_STACKTOP_BEFORE_BAREBOX)
+ stacktop = (ulong)prestack_runtime_address(__image_start);
+ else if (stacktop == PBL_STACKTOP_AFTER_BAREBOX)
+ stacktop = (ulong)prestack_runtime_address(__image_end) +
+ CONFIG_STACK_SIZE;
+
+ stacktop = ALIGN_DOWN(stacktop, 16);
+
+ if (stacktop)
+ call_with_stack(stacktop, pbl_head_cpu_init_v1,
+ stage_ul, entry_data);
+ else
+ pbl_head_cpu_init_v1(stage_ul, entry_data);
+}
+
+/* Runs on the stage's stack, but still unrelocated */
+static void __noreturn __prereloc
+pbl_head_cpu_init_v1(ulong stage_ul, ulong entry_data)
+{
+ struct pbl_stage *stage = (void *)stage_ul;
+
+ if (stage && stage->prereloc_cpu_init) {
+ pbl_board_fn_t cpu_init;
+
+ cpu_init = (void *)((ulong)stage->prereloc_cpu_init +
+ get_runtime_offset());
+ cpu_init();
+ }
+
+ pbl_head_reloc_v1(stage_ul, entry_data);
+}
+
+void __prereloc noinline
+pbl_head_reloc_v1(ulong stage_ul, ulong entry_data)
+{
+ struct pbl_stage *stage = (void *)stage_ul;
+
+ if (!stage)
+ __hang();
+
+ if (stage->relocaddr)
+ relocate_to_adr_full(stage->relocaddr);
+ else
+ relocate_to_current_adr();
+
+ setup_c();
+
+ pbl_head_entry_v1(entry_data);
+}
+
+/* noinline, so &__pbl_board_info isn't computed before relocation */
+static void noinline pbl_head_entry_v1(ulong entry_data)
+{
+ struct pbl_board_info *pbi = &__pbl_board_info;
+ struct pbl_stage *stage;
+ int stage_idx;
+
+ stage_idx = pbl_determine_stage_idx(pbi);
+ stage = pbl_stage(pbi, stage_idx);
+ if (!stage)
+ hang();
+
+ /* A multi-SoC image may still override this from its board init */
+ pbl_set_soc_type(pbi->soc_type);
+ pbl_handoff_soc_type();
+
+ current_stage = stage;
+
+ if (stage->soc_init)
+ stage->soc_init();
+ if (stage->board_init)
+ stage->board_init(entry_data);
+ if (stage->soc_chainload)
+ stage->soc_chainload(stage->board_data);
+
+ hang();
+}
--
2.47.3
next prev parent reply other threads:[~2026-10-27 7:41 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-27 7:24 [RFC PATCH 00/17] Switch RISC-V to declarative entry points Ahmad Fatoum
2026-10-27 7:24 ` [RFC PATCH 01/17] asm-generic: add registers.h with a pc_inside() helper Ahmad Fatoum
2026-10-27 7:24 ` [RFC PATCH 02/17] RISC-V: add call_with_stack() helper Ahmad Fatoum
2026-10-27 7:24 ` [RFC PATCH 03/17] RISC-V: implement relocate_to_adr_full() Ahmad Fatoum
2026-10-27 7:24 ` [RFC PATCH 04/17] PBL: add SoC type declaration and handoff support Ahmad Fatoum
2026-10-27 7:24 ` Ahmad Fatoum [this message]
2026-10-27 7:24 ` [RFC PATCH 06/17] PBL: describe the console in the board info and report the stage Ahmad Fatoum
2026-10-27 7:24 ` [RFC PATCH 07/17] RISC-V: make barebox_riscv_entry() non-naked Ahmad Fatoum
2026-10-27 7:24 ` [RFC PATCH 08/17] RISC-V: add PBL heads for the board info stage dispatch Ahmad Fatoum
2026-10-27 7:24 ` [RFC PATCH 09/17] RISC-V: board-dt-2nd: migrate to board info entry point Ahmad Fatoum
2026-10-27 7:24 ` [RFC PATCH 10/17] RISC-V: hifive: " Ahmad Fatoum
2026-10-27 7:24 ` [RFC PATCH 11/17] RISC-V: allow relocating the PBL more than once Ahmad Fatoum
2026-10-27 7:24 ` [RFC PATCH 12/17] RISC-V: nmon: preserve s1-s3 for the caller Ahmad Fatoum
2026-10-27 7:24 ` [RFC PATCH 13/17] RISC-V: beaglev: migrate to board info entry point Ahmad Fatoum
2026-10-27 7:24 ` [RFC PATCH 14/17] RISC-V: allwinner-d1: " Ahmad Fatoum
2026-10-27 7:24 ` [RFC PATCH 15/17] RISC-V: erizo: " Ahmad Fatoum
2026-10-27 7:24 ` [RFC PATCH 16/17] RISC-V: litex-linux: " Ahmad Fatoum
2026-10-27 7:24 ` [RFC PATCH 17/17] Documentation: porting: describe the " Ahmad Fatoum
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261027074014.3280396-6-a.fatoum@barebox.org \
--to=a.fatoum@barebox.org \
--cc=barebox@lists.infradead.org \
--cc=mfe@pengutronix.de \
--cc=mtr@pengutronix.de \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox