From 7afe198fc1edffcda3bc82b13eed2e5f576b60a4 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Thu, 2 Jul 2026 21:15:44 +0900 Subject: [PATCH 1/8] bootconfig: render descendant keys when xbc_snprint_cmdline() root has a value xbc_node_for_each_key_value() walks to the first leaf under @root, and when @root is itself a leaf it yields @root. That happens not only for an empty "kernel {}" subtree, but also when @root carries both a value and subkeys, e.g. kernel = x kernel.foo = bar Here @root ("kernel") is a leaf because its first child is the value node "x", so the iterator returns @root first. Feeding @root back into xbc_node_compose_key_after(root, root) returns -EINVAL, which the only in-kernel caller papers over with a "len <= 0" check -- but the follow-up tools/bootconfig -C user propagates the error and turns such a bootconfig into a build failure. Worse, short-circuiting the whole call on a leaf @root would silently drop the valid "kernel.foo = bar" descendant that this patch should render. Skip @root inside the loop instead of bailing out: the value-only entry is dropped (it is rendered through the "kernel" cmdline path, not here), while real descendant keys are still emitted. An entirely empty subtree now renders nothing and returns 0 rather than -EINVAL, matching the "nothing to render is not an error" semantics expected by the new build-time caller. Link: https://lore.kernel.org/all/20260626-bootconfig_using_tools-v7-2-24ab72139c29@debian.org/ Signed-off-by: Breno Leitao Signed-off-by: Masami Hiramatsu (Google) --- lib/bootconfig.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/bootconfig.c b/lib/bootconfig.c index 2ed9ee3dc81c..926094d97397 100644 --- a/lib/bootconfig.c +++ b/lib/bootconfig.c @@ -440,6 +440,17 @@ int __init xbc_snprint_cmdline(char *buf, size_t size, struct xbc_node *root) * itself is well defined and returns the would-be length. */ xbc_node_for_each_key_value(root, knode, val) { + /* + * An empty or value-only @root (e.g. "kernel {}" or + * "kernel = x", possibly alongside "kernel.foo = bar") + * yields @root itself here. Skip it: composing a key for it + * would fail with -EINVAL, yet any real descendant keys must + * still be rendered. An entirely empty subtree then renders + * nothing and returns 0 rather than an error. + */ + if (knode == root) + continue; + ret = xbc_node_compose_key_after(root, knode, xbc_namebuf, XBC_KEYLEN_MAX); if (ret < 0) From fc9d90470463f165a95becbfbad21795fd9752bd Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Thu, 2 Jul 2026 21:15:45 +0900 Subject: [PATCH 2/8] bootconfig: render embedded bootconfig as a kernel cmdline at build time Add the build-time pipeline that renders the "kernel" subtree of CONFIG_BOOT_CONFIG_EMBED_FILE into a flat cmdline string and stashes it in .init.rodata as embedded_kernel_cmdline[]. A follow-up patch adds the runtime helper that prepends this string to boot_command_line during early architecture setup so parse_early_param() sees the values. The build wires up: tools/bootconfig -C kernel - userspace tool already shared with lib/bootconfig.c, used here in -C mode to render a bootconfig file to a cmdline lib/embedded-cmdline.S - .incbin's the rendered text plus a NUL (listed under the EXTRA BOOT CONFIG MAINTAINERS entry) lib/Makefile rule - runs tools/bootconfig at build time Makefile prepare dep - ensures tools/bootconfig is built first, same pattern as tools/objtool and tools/bpf/resolve_btfids Drop the test target from tools/bootconfig/Makefile's default 'all' recipe so that hooking the binary into the kernel build does not run test-bootconfig.sh on every prepare. The tests stay available as 'make -C tools/bootconfig test', matching the convention of tools/objtool and tools/bpf/resolve_btfids whose 'all' targets only build the binary. Require BOOT_CONFIG_EMBED_FILE to be non-empty before the new option can be enabled, otherwise tools/bootconfig -C runs against an empty file and prints a parse error on every kernel build. The feature gates on CONFIG_ARCH_SUPPORTS_CMDLINE_FROM_BOOTCONFIG, a silent symbol arches select once they've wired the prepend call into setup_arch(). No arch selects it in this patch, so the user-visible CONFIG_CMDLINE_FROM_BOOTCONFIG is not yet enableable; when an arch later opts in, the runtime behavior is added by the follow-up patches. tools/bootconfig also installs on target systems, so its own Makefile keeps $(CC) and stays cross-buildable as a standalone tool. The kernel build, which runs the tool on the build host during prepare, instead forces CC=$(HOSTCC) from a dedicated tools/bootconfig rule and clears CROSS_COMPILE= in the sub-make. Without that clear, an LLVM=1 cross build would inherit CROSS_COMPILE and tools/scripts/Makefile.include would inject --target=/--sysroot= flags into the host clang invocation, producing a target binary that fails to exec ("Exec format error"). embedded-cmdline.S places the rendered string in its own .init.rodata subsection (.init.rodata.embed_cmdline) with the "a" (allocatable, read-only) flag and %progbits. lib/bootconfig-data.S already places the embedded bootconfig blob in .init.rodata with the "aw" flag (xbc_init() rewrites separators in place, so that data must be writable). Using a distinct subsection name avoids the ld.lld section- type mismatch that would otherwise arise from mixing "a" and "aw" under the same name; the linker's "*(.init.rodata .init.rodata.*)" glob still folds both into the init image and frees them after boot. A follow-up patch wires the build-time tools/bootconfig into the top-level clean target. Link: https://lore.kernel.org/all/20260626-bootconfig_using_tools-v7-3-24ab72139c29@debian.org/ Reviewed-by: Nicolas Schier Signed-off-by: Breno Leitao Signed-off-by: Masami Hiramatsu (Google) --- MAINTAINERS | 1 + Makefile | 16 ++++++++++++++++ init/Kconfig | 36 ++++++++++++++++++++++++++++++++++++ lib/Makefile | 16 ++++++++++++++++ lib/embedded-cmdline.S | 16 ++++++++++++++++ tools/bootconfig/Makefile | 2 +- 6 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 lib/embedded-cmdline.S diff --git a/MAINTAINERS b/MAINTAINERS index 15011f5752a9..45adc7ea76dc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9846,6 +9846,7 @@ F: fs/proc/bootconfig.c F: include/linux/bootconfig.h F: lib/bootconfig-data.S F: lib/bootconfig.c +F: lib/embedded-cmdline.S F: tools/bootconfig/* F: tools/bootconfig/scripts/* diff --git a/Makefile b/Makefile index b9c5792c79e0..f3598f2bc0b6 100644 --- a/Makefile +++ b/Makefile @@ -1552,6 +1552,22 @@ prepare: tools/bpf/resolve_btfids endif endif +# tools/bootconfig renders the embedded bootconfig into a cmdline at build time. +ifdef CONFIG_CMDLINE_FROM_BOOTCONFIG +prepare: tools/bootconfig +endif + +# tools/bootconfig is run on the build host during prepare, so force a host +# binary here; its own Makefile keeps $(CC) for standalone and cross builds. +# CROSS_COMPILE= is cleared so tools/scripts/Makefile.include does not inject +# the target's --target=/--sysroot= flags into the host clang invocation under +# LLVM=1 cross builds (which would produce a target binary that fails to exec). +tools/bootconfig: export CC := $(HOSTCC) +tools/bootconfig: FORCE + $(Q)mkdir -p $(objtree)/tools + $(Q)$(MAKE) O=$(abspath $(objtree)) subdir=tools -C $(srctree)/tools/ \ + bootconfig CROSS_COMPILE= + # The tools build system is not a part of Kbuild and tends to introduce # its own unique issues. If you need to integrate a new tool into Kbuild, # please consider locating that tool outside the tools/ tree and using the diff --git a/init/Kconfig b/init/Kconfig index 5230d4879b1c..598690ec313a 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -1566,6 +1566,42 @@ config BOOT_CONFIG_EMBED_FILE This bootconfig will be used if there is no initrd or no other bootconfig in the initrd. +config ARCH_SUPPORTS_CMDLINE_FROM_BOOTCONFIG + bool + help + Silent symbol; no C code reads it directly. Architectures + select it once their setup_arch() calls + xbc_prepend_embedded_cmdline() before parse_early_param(). + Its only role is to gate the user-visible + CMDLINE_FROM_BOOTCONFIG option per-arch, the same + ARCH_SUPPORTS_* idiom used by ARCH_SUPPORTS_CFI, etc. + +config CMDLINE_FROM_BOOTCONFIG + bool "Render embedded bootconfig as kernel cmdline at build time" + depends on BOOT_CONFIG_EMBED_FILE != "" + depends on ARCH_SUPPORTS_CMDLINE_FROM_BOOTCONFIG + depends on CMDLINE = "" + default n + help + Render the "kernel" subtree of the embedded bootconfig file into a + flat cmdline string at kernel build time and prepend it to + boot_command_line during early architecture setup. This makes + early_param() handlers (e.g. mem=, earlycon=, loglevel=) see the + values supplied via the embedded bootconfig. + + The runtime bootconfig parser is unaffected, so tree-structured + consumers such as ftrace boot-time tracing keep working. + + Note: when an initrd also carries a bootconfig, its "kernel" + subtree is still parsed at runtime, but the embedded "kernel" + keys remain in boot_command_line for parse_early_param() and + end up later than the initrd keys in saved_command_line, so + parse_args() last-wins favors the embedded values. If you need + initrd to override embedded kernel.* keys, leave this option + off. + + If unsure, say N. + config CMDLINE_LOG_WRAP_IDEAL_LEN int "Length to try to wrap the cmdline when logged at boot" default 1021 diff --git a/lib/Makefile b/lib/Makefile index 7f75cc6edf94..4ccdce2fd5e5 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -273,6 +273,22 @@ filechk_defbconf = cat $(or $(real-prereqs), /dev/null) $(obj)/default.bconf: $(CONFIG_BOOT_CONFIG_EMBED_FILE) FORCE $(call filechk,defbconf) +obj-$(CONFIG_CMDLINE_FROM_BOOTCONFIG) += embedded-cmdline.o +$(obj)/embedded-cmdline.o: $(obj)/embedded_cmdline.bin + +# Render the bootconfig "kernel" subtree to a flat cmdline string using +# the userspace tools/bootconfig parser (-C mode). The runtime prepend +# helper enforces COMMAND_LINE_SIZE at boot, so no build-time size +# check is performed here (COMMAND_LINE_SIZE is an arch header +# constant, not a Kconfig value). +quiet_cmd_render_cmdline = BCONF2C $@ + cmd_render_cmdline = \ + $(objtree)/tools/bootconfig/bootconfig -C $< > $@ + +targets += embedded_cmdline.bin +$(obj)/embedded_cmdline.bin: $(obj)/default.bconf $(objtree)/tools/bootconfig/bootconfig FORCE + $(call if_changed,render_cmdline) + obj-$(CONFIG_RBTREE_TEST) += rbtree_test.o obj-$(CONFIG_INTERVAL_TREE_TEST) += interval_tree_test.o diff --git a/lib/embedded-cmdline.S b/lib/embedded-cmdline.S new file mode 100644 index 000000000000..bda81b4a42be --- /dev/null +++ b/lib/embedded-cmdline.S @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Embed the build-time-rendered bootconfig "kernel" subtree as a flat + * cmdline string. setup_arch() prepends this to boot_command_line on + * architectures that select ARCH_SUPPORTS_CMDLINE_FROM_BOOTCONFIG. + * + * Copyright (c) 2026 Meta Platforms, Inc. and affiliates + * Copyright (c) 2026 Breno Leitao + */ + .section .init.rodata.embed_cmdline, "a", %progbits + .global embedded_kernel_cmdline +embedded_kernel_cmdline: + .incbin "lib/embedded_cmdline.bin" + .byte 0 + .global embedded_kernel_cmdline_end +embedded_kernel_cmdline_end: diff --git a/tools/bootconfig/Makefile b/tools/bootconfig/Makefile index 90eb47c9d8de..4e82fd9553cd 100644 --- a/tools/bootconfig/Makefile +++ b/tools/bootconfig/Makefile @@ -15,7 +15,7 @@ override CFLAGS += -Wall -g -I$(CURDIR)/include ALL_TARGETS := bootconfig ALL_PROGRAMS := $(patsubst %,$(OUTPUT)%,$(ALL_TARGETS)) -all: $(ALL_PROGRAMS) test +all: $(ALL_PROGRAMS) $(OUTPUT)bootconfig: main.c include/linux/bootconfig.h $(LIBSRC) $(CC) $(filter %.c,$^) $(CFLAGS) $(LDFLAGS) -o $@ From 1bbab483d30013e1a393dde547ff10fc4438b208 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Thu, 2 Jul 2026 21:15:45 +0900 Subject: [PATCH 3/8] bootconfig: clean build-time tools/bootconfig from make clean The previous patch builds tools/bootconfig during 'make prepare' to render the embedded bootconfig cmdline, but nothing removes it on 'make clean', leaving the compiled tool and its objects behind. Wire a bootconfig_clean hook into the top-level clean target so the compiled tool and its objects are removed by make clean, matching the prepare-wired tools/objtool and tools/bpf/resolve_btfids. The hook runs tools/bootconfig's Makefile via $(MAKE), which the kernel build invokes with -rR (MAKEFLAGS += -rR). -rR drops the built-in $(RM) variable, so the existing "$(RM) -f ..." clean recipe would expand to a bare "-f ..." and fail. Spell the recipe with a literal "rm -f" so it keeps working both standalone and when invoked from Kbuild. Link: https://lore.kernel.org/all/20260626-bootconfig_using_tools-v7-4-24ab72139c29@debian.org/ Reviewed-by: Nicolas Schier Signed-off-by: Breno Leitao Signed-off-by: Masami Hiramatsu (Google) --- Makefile | 11 ++++++++++- tools/bootconfig/Makefile | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index f3598f2bc0b6..a88ff3e3b72d 100644 --- a/Makefile +++ b/Makefile @@ -1594,6 +1594,15 @@ ifneq ($(wildcard $(objtool_O)),) $(Q)$(MAKE) -sC $(abs_srctree)/tools/objtool O=$(objtool_O) srctree=$(abs_srctree) $(patsubst objtool_%,%,$@) endif +PHONY += bootconfig_clean + +bootconfig_O = $(abspath $(objtree))/tools/bootconfig + +bootconfig_clean: +ifneq ($(wildcard $(bootconfig_O)),) + $(Q)$(MAKE) -sC $(srctree)/tools/bootconfig O=$(bootconfig_O) clean +endif + tools/: FORCE $(Q)mkdir -p $(objtree)/tools $(Q)$(MAKE) O=$(abspath $(objtree)) subdir=tools -C $(srctree)/tools/ @@ -1769,7 +1778,7 @@ vmlinuxclean: $(Q)$(CONFIG_SHELL) $(srctree)/scripts/link-vmlinux.sh clean $(Q)$(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) clean) -clean: archclean vmlinuxclean resolve_btfids_clean objtool_clean +clean: archclean vmlinuxclean resolve_btfids_clean objtool_clean bootconfig_clean # mrproper - Delete all generated files, including .config # diff --git a/tools/bootconfig/Makefile b/tools/bootconfig/Makefile index 4e82fd9553cd..3cb8066d5141 100644 --- a/tools/bootconfig/Makefile +++ b/tools/bootconfig/Makefile @@ -27,4 +27,4 @@ install: $(ALL_PROGRAMS) install $(OUTPUT)bootconfig $(DESTDIR)$(bindir) clean: - $(RM) -f $(OUTPUT)*.o $(ALL_PROGRAMS) + rm -f $(OUTPUT)*.o $(ALL_PROGRAMS) From 462afde2150fface1122120af233e0be88e06814 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Thu, 2 Jul 2026 21:15:45 +0900 Subject: [PATCH 4/8] bootconfig: add xbc_prepend_embedded_cmdline() helper Add a helper that prepends the build-time-rendered embedded bootconfig "kernel" subtree (embedded_kernel_cmdline[] from embedded-cmdline.S) to a cmdline buffer with a separating space. Architectures call this from setup_arch() before parse_early_param() so early_param() handlers (mem=, earlycon=, loglevel=, ...) see values supplied via the embedded bootconfig. The in-place prepend (shift the existing string right, then drop the embedded string in front) is factored into a small str_prepend() helper. On overflow the helper logs an error and leaves the cmdline untouched rather than panicking. Booting without the embedded values is better than refusing to boot, and the error tells the user why their embedded keys are missing. The helper records whether it actually prepended, exposed via xbc_embedded_cmdline_applied(). setup_boot_config() uses this to decide whether the runtime "kernel" render would duplicate keys already folded into boot_command_line. Also add bootconfig_cmdline_requested(), a small parse_args() wrapper that reports whether "bootconfig" was passed on the command line and, via an optional out-parameter, where the "--" init arguments begin. setup_arch() and setup_boot_config() share it so the early and late paths agree on the opt-in. It sits under CONFIG_BOOT_CONFIG rather than CONFIG_CMDLINE_FROM_BOOTCONFIG because the runtime parser needs it on every bootconfig build. When CONFIG_CMDLINE_FROM_BOOTCONFIG=n, the public declaration in resolves to a no-op stub so callers compile unchanged. Link: https://lore.kernel.org/all/20260626-bootconfig_using_tools-v7-5-24ab72139c29@debian.org/ Signed-off-by: Breno Leitao Signed-off-by: Masami Hiramatsu (Google) --- include/linux/bootconfig.h | 14 ++++ lib/bootconfig.c | 128 ++++++++++++++++++++++++++++++++++++- 2 files changed, 141 insertions(+), 1 deletion(-) diff --git a/include/linux/bootconfig.h b/include/linux/bootconfig.h index 1c7f3b74ffcf..deda507500da 100644 --- a/include/linux/bootconfig.h +++ b/include/linux/bootconfig.h @@ -308,4 +308,18 @@ static inline const char *xbc_get_embedded_bootconfig(size_t *size) } #endif +/* Bootconfig opt-in detection, shared by setup_arch() and setup_boot_config() */ +#ifdef CONFIG_BOOT_CONFIG +bool __init bootconfig_cmdline_requested(const char *boot_cmdline, int *end_offset); +#endif + +/* Build-time-rendered bootconfig cmdline prepended in setup_arch() */ +#ifdef CONFIG_CMDLINE_FROM_BOOTCONFIG +void __init xbc_prepend_embedded_cmdline(char *dst, size_t size); +bool __init xbc_embedded_cmdline_applied(void); +#else +static inline void xbc_prepend_embedded_cmdline(char *dst, size_t size) { } +static inline bool xbc_embedded_cmdline_applied(void) { return false; } +#endif + #endif diff --git a/lib/bootconfig.c b/lib/bootconfig.c index 926094d97397..89c88e359179 100644 --- a/lib/bootconfig.c +++ b/lib/bootconfig.c @@ -19,9 +19,13 @@ #include #include #include +#include +#include +#include #include #include #include +#include /* COMMAND_LINE_SIZE */ #ifdef CONFIG_BOOT_CONFIG_EMBED /* embedded_bootconfig_data is defined in bootconfig-data.S */ @@ -34,7 +38,129 @@ const char * __init xbc_get_embedded_bootconfig(size_t *size) return (*size) ? embedded_bootconfig_data : NULL; } #endif -#endif + +#ifdef CONFIG_CMDLINE_FROM_BOOTCONFIG +/* embedded_kernel_cmdline is defined in embedded-cmdline.S */ +extern __visible const char embedded_kernel_cmdline[]; +extern __visible const char embedded_kernel_cmdline_end[]; + +/* Set once the embedded cmdline has actually been prepended. */ +static bool xbc_cmdline_applied __initdata; + +/* + * str_prepend() - Prepend @src in front of the string in @dst, in place + * @dst: NUL-terminated destination buffer, currently @dst_len bytes long + * @dst_len: length of the current @dst string (excluding its NUL) + * @src: bytes to prepend (not NUL-terminated) + * @src_len: number of bytes from @src to prepend + * + * The caller must guarantee @dst has room for src_len + dst_len + 1 bytes. + * Moving dst_len + 1 bytes carries @dst's NUL terminator too, so an empty + * @dst needs no special case. + */ +static void __init str_prepend(char *dst, size_t dst_len, + const char *src, size_t src_len) +{ + memmove(dst + src_len, dst, dst_len + 1); + memcpy(dst, src, src_len); +} + +/** + * xbc_prepend_embedded_cmdline() - Prepend embedded bootconfig cmdline + * @dst: cmdline buffer to prepend into (must already contain a NUL byte) + * @size: total capacity of @dst in bytes + * + * Prepend the build-time-rendered "kernel" subtree of the embedded + * bootconfig to @dst. The rendered string already ends with a single + * space (the xbc_snprint_cmdline() invariant), which serves as the + * separator between the embedded keys and any existing content of @dst. + * On overflow, log an error and leave @dst untouched rather than + * silently truncating: booting without the embedded values is better + * than refusing to boot, and the error message tells the user why + * their embedded keys are missing. + * + * Intended to be called from setup_arch() before parse_early_param() so + * that early_param() handlers see the embedded values. + */ +void __init xbc_prepend_embedded_cmdline(char *dst, size_t size) +{ + size_t embed_len = embedded_kernel_cmdline_end - embedded_kernel_cmdline; + size_t dst_len; + + if (!size || embed_len <= 1) /* trailing NUL only */ + return; + embed_len--; /* exclude trailing NUL byte */ + + dst_len = strnlen(dst, size); + if (embed_len + dst_len + 1 > size) { + pr_err("embedded bootconfig cmdline (%zu bytes) does not fit in COMMAND_LINE_SIZE with %zu bytes already used; ignoring embedded values\n", + embed_len, dst_len); + return; + } + + str_prepend(dst, dst_len, embedded_kernel_cmdline, embed_len); + xbc_cmdline_applied = true; +} + +/** + * xbc_embedded_cmdline_applied() - Did the embedded cmdline get prepended? + * + * Return true if xbc_prepend_embedded_cmdline() actually prepended the + * embedded "kernel" subtree. setup_boot_config() uses this to avoid + * rendering the same keys a second time. + */ +bool __init xbc_embedded_cmdline_applied(void) +{ + return xbc_cmdline_applied; +} +#endif /* CONFIG_CMDLINE_FROM_BOOTCONFIG */ + +/* parse_args() callback: flag when the "bootconfig" parameter is present. */ +static int __init bootconfig_optin(char *param, char *val, + const char *unused, void *arg) +{ + if (!strcmp(param, "bootconfig")) + *(bool *)arg = true; + return 0; +} + +/** + * bootconfig_cmdline_requested() - Was "bootconfig" passed on the cmdline? + * @boot_cmdline: kernel command line to inspect (not modified) + * @end_offset: if non-NULL, set to the offset of the init arguments that + * follow a "--" separator, or 0 when there is none + * + * Parse a private copy of @boot_cmdline (parse_args() is destructive) and + * report whether "bootconfig" is present before the "--" separator. + * setup_arch() uses this to gate prepending the build-time embedded cmdline; + * setup_boot_config() uses it for the runtime opt-in and to locate the init + * arguments via @end_offset. Sharing one parser keeps the early and late + * paths agreeing on what counts as opt-in. CONFIG_BOOT_CONFIG_FORCE is not + * folded in here; callers apply it where they need it. + */ +bool __init bootconfig_cmdline_requested(const char *boot_cmdline, int *end_offset) +{ + static char tmp_cmdline[COMMAND_LINE_SIZE] __initdata; + bool found = false; + char *err; + + if (end_offset) + *end_offset = 0; + + strscpy(tmp_cmdline, boot_cmdline, COMMAND_LINE_SIZE); + err = parse_args("bootconfig", tmp_cmdline, NULL, 0, 0, 0, + &found, bootconfig_optin); + if (IS_ERR(err)) + return false; + + /* parse_args() stops at "--" and returns the address of the rest. */ + if (end_offset && err) + *end_offset = err - tmp_cmdline; + + return found; +} + +#endif /* __KERNEL__ */ /* * Extra Boot Config (XBC) is given as tree-structured ascii text of From de0428ed555b0d323c07bf750acf261a0ac772c9 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Thu, 2 Jul 2026 21:15:45 +0900 Subject: [PATCH 5/8] Documentation: bootconfig: document build-time cmdline rendering Add a section describing CONFIG_CMDLINE_FROM_BOOTCONFIG: what it does (renders the embedded "kernel" subtree to a flat cmdline at build time so early_param() handlers see the values), what it requires (BOOT_CONFIG_EMBED, a non-empty BOOT_CONFIG_EMBED_FILE, CONFIG_CMDLINE to be empty, and ARCH_SUPPORTS_CMDLINE_FROM_BOOTCONFIG -- currently x86 only), the bootconfig opt-in semantics, the initrd-vs-embedded precedence, and the soft-error overflow behavior. This addresses feedback from the Sashiko AI review and Masami Hiramatsu to document the CONFIG_CMDLINE requirement, which is enforced at the Kconfig level but was not mentioned in the documentation, potentially confusing users who might satisfy all other requirements but still find the option hidden in menuconfig if CONFIG_CMDLINE is non-empty. Link: https://lore.kernel.org/all/20260626-bootconfig_using_tools-v7-6-24ab72139c29@debian.org/ Signed-off-by: Breno Leitao Signed-off-by: Masami Hiramatsu (Google) --- Documentation/admin-guide/bootconfig.rst | 81 ++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/Documentation/admin-guide/bootconfig.rst b/Documentation/admin-guide/bootconfig.rst index f712758472d5..3d6412458c8b 100644 --- a/Documentation/admin-guide/bootconfig.rst +++ b/Documentation/admin-guide/bootconfig.rst @@ -234,6 +234,87 @@ Kconfig option selected. Note that even if you set this option, you can override the embedded bootconfig by another bootconfig which attached to the initrd. +Rendering Embedded kernel.* Keys at Build Time +---------------------------------------------- + +By default, the embedded bootconfig (``CONFIG_BOOT_CONFIG_EMBED=y``) is +parsed at runtime, after ``parse_early_param()`` has already run. Early +parameter handlers (``mem=``, ``earlycon=``, ``loglevel=``, ...) therefore +cannot see values supplied via the embedded ``kernel`` subtree. + +``CONFIG_CMDLINE_FROM_BOOTCONFIG`` resolves this by rendering the +``kernel`` subtree of ``CONFIG_BOOT_CONFIG_EMBED_FILE`` into a flat cmdline +string at kernel build time (via ``tools/bootconfig -C``) and prepending +it to ``boot_command_line`` during early architecture setup, so the keys +are visible to ``parse_early_param()``. + +The option requires ``CONFIG_BOOT_CONFIG_EMBED=y``, a non-empty +``CONFIG_BOOT_CONFIG_EMBED_FILE``, ``CONFIG_CMDLINE`` to be empty, and +an architecture that selects ``CONFIG_ARCH_SUPPORTS_CMDLINE_FROM_BOOTCONFIG``. +Currently only x86 selects it; on other architectures the embedded +bootconfig still works, but only through the late runtime parser. + +The same ``bootconfig`` opt-in applies as elsewhere: the rendered keys +are prepended only when ``bootconfig`` (in any form) appears on the +kernel command line, or when ``CONFIG_BOOT_CONFIG_FORCE`` is set, which +defaults to ``y`` when ``CONFIG_BOOT_CONFIG_EMBED`` is set. + +For example, given:: + + kernel { + loglevel = 7 + mem = 4G + } + +the kernel boots as if ``loglevel=7 mem=4G`` had been prepended to the +bootloader command line, with the values visible to early-parsed +handlers. Comma-separated values are still expanded into multiple +cmdline entries per the bootconfig array convention -- the embedded +``kernel.earlycon = "uart8250,io,0x3f8"`` must be quoted to land as a +single ``earlycon=`` entry, exactly as for the runtime parser. + +If the rendered string would not fit in ``COMMAND_LINE_SIZE`` together +with the existing command line, the prepend is skipped and an error is +logged, so an oversized embedded bootconfig cannot brick a boot. + +Interaction with other command line and bootconfig sources +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +With ``CONFIG_CMDLINE_FROM_BOOTCONFIG=y`` the rendered ``kernel`` +subtree behaves like a build-time command line (similar to +``CONFIG_CMDLINE``), not like a bootconfig source. It is prepended to +``boot_command_line`` in ``setup_arch()``, before ``parse_early_param()`` +and long before the runtime parser looks at an initrd. Options can reach +the kernel from up to four places: + +- Bootloader command line: the arguments the boot loader passes. The + embedded cmdline is prepended in front of them, so for last-one-wins + parameters a bootloader option still overrides the embedded value. + Visible in /proc/cmdline. +- Embedded cmdline (this option): the rendered ``kernel`` subtree, + prepended early so it is seen by ``parse_early_param()``. Visible in + /proc/cmdline. +- Initrd bootconfig: parsed late in ``setup_boot_config()``; its + ``kernel`` keys are placed ahead of ``boot_command_line``, i.e. before + the embedded cmdline, so last-wins favors the embedded values. As a + bootconfig source, an initrd bootconfig still replaces the embedded + bootconfig. Visible in /proc/cmdline and /proc/bootconfig. +- Embedded bootconfig (runtime): parsed late, only when no initrd + bootconfig is present. Visible in /proc/cmdline and /proc/bootconfig. + +So with this option the embedded ``kernel.*`` values take precedence +over an initrd bootconfig's ``kernel.*`` values: for early parameters +the initrd is not parsed yet, and for ordinary parameters the embedded +keys land later in the command line. If you need an initrd bootconfig to +override the embedded ``kernel.*`` keys, leave this option off and rely +on the runtime parser. + +The rendered string is part of the command line, so it appears in +/proc/cmdline. It is deliberately not shown in /proc/bootconfig: that +file keeps reporting the parsed bootconfig tree -- the initrd bootconfig +if present, otherwise the embedded bootconfig -- independent of whether +build-time cmdline rendering is enabled. + Kernel parameters via Boot Config ================================= From bfdf7ec54f1a5e4759a5550ceed3edf738f36c5f Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Thu, 2 Jul 2026 21:15:45 +0900 Subject: [PATCH 6/8] x86/setup: prepend embedded bootconfig cmdline before parse_early_param Call xbc_prepend_embedded_cmdline() in setup_arch() right after the CONFIG_CMDLINE merge and before strscpy(command_line, ...) so the build-time-rendered embedded bootconfig "kernel" subtree is part of boot_command_line by the time parse_early_param() runs. early_param() handlers (mem=, earlycon=, loglevel=, ...) now see values supplied via CONFIG_BOOT_CONFIG_EMBED_FILE without parsing bootconfig at runtime. Gate the prepend on the same opt-in the runtime parser uses: prepend when "bootconfig" is present on the command line, or when CONFIG_BOOT_CONFIG_FORCE is set. Detect it with parse_args(), exactly as setup_boot_config() does, so both agree on what counts as opt-in: any "bootconfig" key regardless of value (bare, =0, =1, ...), and only before the "--" that separates init arguments. Sharing the parser keeps the early and late paths from diverging -- e.g. "bootconfig=0" or a "-- bootconfig" meant for init must not apply the embedded keys early while the runtime parser skips them. The prepend necessarily runs before setup_boot_config() detects an initrd bootconfig, so an initrd cannot override the embedded "kernel" keys for early_param(). This is intentional: the embedded cmdline acts like a build-time CONFIG_CMDLINE. An initrd bootconfig's "kernel" keys never reached early_param() anyway (they apply late via extra_command_line), so nothing is lost -- the initrd keys still apply late, with last-wins keeping the embedded values in effect. Link: https://lore.kernel.org/all/20260626-bootconfig_using_tools-v7-7-24ab72139c29@debian.org/ Signed-off-by: Breno Leitao Signed-off-by: Masami Hiramatsu (Google) --- arch/x86/Kconfig | 1 + arch/x86/kernel/setup.c | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index bdad90f210e4..fd979e0be1f0 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -127,6 +127,7 @@ config X86 select ARCH_SUPPORTS_NUMA_BALANCING if X86_64 select ARCH_SUPPORTS_KMAP_LOCAL_FORCE_MAP if NR_CPUS <= 4096 select ARCH_SUPPORTS_CFI if X86_64 + select ARCH_SUPPORTS_CMDLINE_FROM_BOOTCONFIG select ARCH_USES_CFI_TRAPS if X86_64 && CFI select ARCH_SUPPORTS_LTO_CLANG select ARCH_SUPPORTS_LTO_CLANG_THIN diff --git a/arch/x86/kernel/setup.c b/arch/x86/kernel/setup.c index 46882ce79c3a..88b055a46591 100644 --- a/arch/x86/kernel/setup.c +++ b/arch/x86/kernel/setup.c @@ -6,6 +6,7 @@ * parts of early kernel initialization. */ #include +#include #include #include #include @@ -880,7 +881,6 @@ static void __init x86_report_nx(void) * * Note: On x86_64, fixmaps are ready for use even before this is called. */ - void __init setup_arch(char **cmdline_p) { #ifdef CONFIG_X86_32 @@ -924,6 +924,18 @@ void __init setup_arch(char **cmdline_p) builtin_cmdline_added = true; #endif +#ifdef CONFIG_CMDLINE_FROM_BOOTCONFIG + /* + * Prepend the build-time-rendered embedded "kernel" keys here so + * parse_early_param() below sees them, using the same opt-in as the + * runtime parser, plus the build-time CONFIG_BOOT_CONFIG_FORCE. + */ + if (bootconfig_cmdline_requested(boot_command_line, NULL) || + IS_ENABLED(CONFIG_BOOT_CONFIG_FORCE)) + xbc_prepend_embedded_cmdline(boot_command_line, + COMMAND_LINE_SIZE); +#endif + strscpy(command_line, boot_command_line, COMMAND_LINE_SIZE); *cmdline_p = command_line; From 378517ca3be0477e0da056f9e13df1aa37b88702 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Thu, 2 Jul 2026 21:15:46 +0900 Subject: [PATCH 7/8] bootconfig: skip runtime kernel.* render once prepended early setup_boot_config() folds the embedded bootconfig "kernel" subtree into the command line via xbc_make_cmdline("kernel"). A subsequent patch lets an architecture prepend the build-time-rendered embedded "kernel" keys to boot_command_line early in setup_arch(); rendering them again here would then duplicate every key in saved_command_line and make accumulating handlers (console=, earlycon=, ...) re-register the same value. Track whether the bootconfig data came from the embedded source (from_embedded) and skip the runtime render only when the early prepend actually happened, as reported by xbc_embedded_cmdline_applied(). On architectures that do not select ARCH_SUPPORTS_CMDLINE_FROM_BOOTCONFIG that helper is a stub returning false, so this path is unchanged and the embedded "kernel" keys still reach the cmdline via the runtime parser exactly as before. Link: https://lore.kernel.org/all/20260626-bootconfig_using_tools-v7-8-24ab72139c29@debian.org/ Signed-off-by: Breno Leitao Signed-off-by: Masami Hiramatsu (Google) --- init/main.c | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/init/main.c b/init/main.c index e363232b428b..260bd5242f94 100644 --- a/init/main.c +++ b/init/main.c @@ -378,12 +378,15 @@ static void __init setup_boot_config(void) int pos, ret; size_t size; char *err; + bool from_embedded = false; /* Cut out the bootconfig data even if we have no bootconfig option */ data = get_boot_config_from_initrd(&size); /* If there is no bootconfig in initrd, try embedded one. */ - if (!data) + if (!data) { data = xbc_get_embedded_bootconfig(&size); + from_embedded = true; + } strscpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE); err = parse_args("bootconfig", tmp_cmdline, NULL, 0, 0, 0, NULL, @@ -421,8 +424,24 @@ static void __init setup_boot_config(void) } else { xbc_get_info(&ret, NULL); pr_info("Load bootconfig: %ld bytes %d nodes\n", (long)size, ret); - /* keys starting with "kernel." are passed via cmdline */ - extra_command_line = xbc_make_cmdline("kernel"); + /* + * keys starting with "kernel." are passed via cmdline. When + * this bootconfig came from the embedded source and + * setup_arch() already prepended the rendered "kernel" subtree + * to boot_command_line, rendering again here would duplicate + * the keys in saved_command_line and make accumulating handlers + * (console=, earlycon=, ...) re-register the same value. Skip + * only when the prepend really happened. + * + * On arches that do not select ARCH_SUPPORTS_CMDLINE_FROM_BOOTCONFIG, + * CONFIG_CMDLINE_FROM_BOOTCONFIG is unselectable and + * xbc_embedded_cmdline_applied() collapses to a stub returning + * false, so this path still runs and the embedded "kernel" + * keys reach the cmdline via the runtime parser exactly as + * before this series. + */ + if (!from_embedded || !xbc_embedded_cmdline_applied()) + extra_command_line = xbc_make_cmdline("kernel"); /* Also, "init." keys are init arguments */ extra_init_args = xbc_make_cmdline("init"); } From 85595d3f964825c833fd2597521fcef67c3071de Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Thu, 2 Jul 2026 21:15:46 +0900 Subject: [PATCH 8/8] init/main.c: use bootconfig_cmdline_requested() for the runtime opt-in setup_boot_config() open-coded the same "is bootconfig requested on the kernel command line?" check that setup_arch() performs via the shared bootconfig_cmdline_requested() helper. Switch it to the helper so the early (setup_arch) and late (setup_boot_config) paths use one parser and cannot disagree on what counts as opt-in. The helper also reports the offset of the init arguments following a "--" separator, which is exactly what initargs_offs needs, so the local parse_args() call, its bootconfig_params() callback and the tmp_cmdline copy are removed. No functional change intended. Link: https://lore.kernel.org/all/20260626-bootconfig_using_tools-v7-9-24ab72139c29@debian.org/ Suggested-by: Masami Hiramatsu (Google) Signed-off-by: Breno Leitao Signed-off-by: Masami Hiramatsu (Google) --- init/main.c | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/init/main.c b/init/main.c index 260bd5242f94..39a518a47242 100644 --- a/init/main.c +++ b/init/main.c @@ -356,28 +356,17 @@ static char * __init xbc_make_cmdline(const char *key) return new_cmdline; } -static int __init bootconfig_params(char *param, char *val, - const char *unused, void *arg) -{ - if (strcmp(param, "bootconfig") == 0) { - bootconfig_found = true; - } - return 0; -} - static int __init warn_bootconfig(char *str) { - /* The 'bootconfig' has been handled by bootconfig_params(). */ + /* The 'bootconfig' option is handled by setup_boot_config(). */ return 0; } static void __init setup_boot_config(void) { - static char tmp_cmdline[COMMAND_LINE_SIZE] __initdata; const char *msg, *data; - int pos, ret; + int pos, ret, offs; size_t size; - char *err; bool from_embedded = false; /* Cut out the bootconfig data even if we have no bootconfig option */ @@ -388,16 +377,12 @@ static void __init setup_boot_config(void) from_embedded = true; } - strscpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE); - err = parse_args("bootconfig", tmp_cmdline, NULL, 0, 0, 0, NULL, - bootconfig_params); - - if (IS_ERR(err) || !(bootconfig_found || IS_ENABLED(CONFIG_BOOT_CONFIG_FORCE))) + bootconfig_found = bootconfig_cmdline_requested(boot_command_line, &offs); + if (!(bootconfig_found || IS_ENABLED(CONFIG_BOOT_CONFIG_FORCE))) return; - /* parse_args() stops at the next param of '--' and returns an address */ - if (err) - initargs_offs = err - tmp_cmdline; + /* Offset of the init arguments after a "--", located by the helper. */ + initargs_offs = offs; if (!data) { /* If user intended to use bootconfig, show an error level message */