[PATCH 2/2] bootconfig: Apply early options from embedded config

From: Petr Malat
Date: Tue Nov 21 2023 - 18:14:20 EST


Eliminate all allocations in embedded config handling to allow calling
it from arch_setup and applying early options.

Config stored in initrd can't be used for early options, because initrd
is set up after early options are processed.

Add this information to the documentation and also to the option
description.

Signed-off-by: Petr Malat <oss@xxxxxxxxx>
---
Documentation/admin-guide/bootconfig.rst | 3 +
init/Kconfig | 4 +-
init/main.c | 141 ++++++++++++++++++-----
lib/bootconfig.c | 20 +++-
4 files changed, 132 insertions(+), 36 deletions(-)

diff --git a/Documentation/admin-guide/bootconfig.rst b/Documentation/admin-guide/bootconfig.rst
index 91339efdcb54..fb085f696f9b 100644
--- a/Documentation/admin-guide/bootconfig.rst
+++ b/Documentation/admin-guide/bootconfig.rst
@@ -161,6 +161,9 @@ Boot Kernel With a Boot Config
There are two options to boot the kernel with bootconfig: attaching the
bootconfig to the initrd image or embedding it in the kernel itself.

+Early options may be specified only in the embedded bootconfig, because
+they are processed before the initrd.
+
Attaching a Boot Config to Initrd
---------------------------------

diff --git a/init/Kconfig b/init/Kconfig
index 9161d2dbad0c..04de756c935e 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -1310,7 +1310,9 @@ config BOOT_CONFIG
Extra boot config allows system admin to pass a config file as
complemental extension of kernel cmdline when booting.
The boot config file must be attached at the end of initramfs
- with checksum, size and magic word.
+ with checksum, size and magic word. Note that early options may
+ be specified in the embedded bootconfig only. Early options
+ specified in initrd bootconfig will not be applied.
See <file:Documentation/admin-guide/bootconfig.rst> for details.

If unsure, say Y.
diff --git a/init/main.c b/init/main.c
index 0cd738f7f0cf..9aac59673a3a 100644
--- a/init/main.c
+++ b/init/main.c
@@ -158,6 +158,9 @@ static size_t initargs_offs;
static char *execute_command;
static char *ramdisk_execute_command = "/init";

+static int __init do_early_param(char *param, char *val,
+ const char *unused, void *arg);
+
/*
* Used to generate warnings if static_key manipulation functions are used
* before jump_label_init is called.
@@ -406,63 +409,134 @@ static int __init warn_bootconfig(char *str)
return 0;
}

-static void __init setup_boot_config(void)
+static void __init boot_config_pr_err(const char *msg, int pos, const char *src)
+{
+ if (pos < 0)
+ pr_err("Failed to init bootconfig: %s.\n", msg);
+ else
+ pr_err("Failed to parse %s bootconfig: %s at %d.\n",
+ src, msg, pos);
+}
+
+static int __init setup_boot_config_early(void)
{
static char tmp_cmdline[COMMAND_LINE_SIZE] __initdata;
- const char *msg, *initrd_data;
- int pos, ret;
- size_t initrd_size, embeded_size = 0;
- char *err, *embeded_data = NULL;
+ static int prev_rtn __initdata;
+ struct xbc_node *root, *knode, *vnode;
+ char *embeded_data, *err;
+ const char *val, *msg;
+ size_t embeded_size;
+ int ret, pos;

- /* Cut out the bootconfig data even if we have no bootconfig option */
- initrd_data = get_boot_config_from_initrd(&initrd_size);
- /* If there is no bootconfig in initrd, try embedded one. */
- if (!initrd_data || IS_ENABLED(CONFIG_BOOT_CONFIG_EMBED_APPEND_INITRD))
- embeded_data = xbc_get_embedded_bootconfig(&embeded_size);
+ if (prev_rtn)
+ return prev_rtn;

+ embeded_data = xbc_get_embedded_bootconfig(&embeded_size);
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)))
- return;
-
+ if (IS_ERR(err) || !(bootconfig_found || IS_ENABLED(CONFIG_BOOT_CONFIG_FORCE))) {
+ prev_rtn = embeded_data ? -ENOMSG : -ENODATA;
+ return prev_rtn;
+ }
/* parse_args() stops at the next param of '--' and returns an address */
if (err)
initargs_offs = err - tmp_cmdline;

- if (!initrd_data && !embeded_data) {
- /* If user intended to use bootconfig, show an error level message */
- if (bootconfig_found)
- pr_err("'bootconfig' found on command line, but no bootconfig found\n");
- else
- pr_info("No bootconfig data provided, so skipping bootconfig");
- return;
+ if (!embeded_data) {
+ prev_rtn = -ENOPROTOOPT;
+ return prev_rtn;
}

ret = xbc_init(embeded_data, embeded_size, &msg, &pos);
- if (ret < 0)
- goto err0;
+ if (ret < 0) {
+ boot_config_pr_err(msg, pos, "embedded");
+ prev_rtn = ret;
+ return prev_rtn;
+ }
+ prev_rtn = 1;
+
+ /* Process early options */
+ root = xbc_find_node("kernel");
+ if (!root)
+ goto out;
+
+ xbc_node_for_each_key_value(root, knode, val) {
+ ret = xbc_node_compose_key_after(root, knode,
+ xbc_namebuf, XBC_KEYLEN_MAX);
+ if (ret < 0)
+ continue;
+
+ vnode = xbc_node_get_child(knode);
+ if (!vnode) {
+ do_early_param(xbc_namebuf, NULL, NULL, NULL);
+ continue;
+ }
+
+ xbc_array_for_each_value(vnode, val) {
+ if (strscpy(tmp_cmdline, val, sizeof(tmp_cmdline)) < 1) {
+ pr_err("Value for '%s' too long\n", xbc_namebuf);
+ break;
+ }
+ do_early_param(xbc_namebuf, tmp_cmdline, NULL, NULL);
+ }
+ }
+
+out: return embeded_data ? 1 : 0;
+}
+
+static void __init setup_boot_config(void)
+{
+ const char *msg, *initrd_data;
+ int pos, ret;
+ size_t initrd_size, s;
+
+ /* Cut out the bootconfig data even if we have no bootconfig option */
+ initrd_data = get_boot_config_from_initrd(&initrd_size);
+
+ ret = setup_boot_config_early();
+ if (ret == -ENOMSG || (ret == -ENODATA && initrd_data)) {
+ pr_info("Bootconfig data present, but handling is disabled\n");
+ return;
+ } else if (ret == -ENODATA) {
+ /* Bootconfig disabled and bootconfig data are not present */
+ return;
+ } else if (ret == -ENOPROTOOPT) {
+ /* Embedded bootconfig not found */
+ if (!initrd_data) {
+ pr_err("'bootconfig' found on command line, but no bootconfig data found\n");
+ return;
+ }
+ ret = xbc_init(NULL, 0, &msg, &pos);
+ if (ret)
+ goto err0;
+ } else if (ret < 0) {
+ /* Other error, should be logged already */
+ return;
+ } else if (initrd_data && !IS_ENABLED(CONFIG_BOOT_CONFIG_EMBED_APPEND_INITRD)) {
+ /* Embedded bootconfig handled, but we should not append to it */
+ xbc_get_info(&ret, &s);
+ pr_info("Replacing embedded bootconfig of %d nodes and %zu bytes.\n", ret, s);
+ xbc_exit();
+ ret = xbc_init(NULL, 0, &msg, &pos);
+ if (ret)
+ goto err0;
+ }

/* Call append even if no data are there as embedded bootconfig is in .init */
ret = xbc_append(initrd_data, initrd_size, &msg, &pos);
if (ret < 0)
goto err0;

- xbc_get_info(&ret, NULL);
- pr_info("Load bootconfig: %ld bytes %d nodes\n", (long)(embeded_size + initrd_size), ret);
+ xbc_get_info(&ret, &s);
+ pr_info("Load bootconfig: %d nodes %zu bytes.\n", ret, s);
/* keys starting with "kernel." are passed via cmdline */
extra_command_line = xbc_make_cmdline("kernel");
/* Also, "init." keys are init arguments */
extra_init_args = xbc_make_cmdline("init");
return;

-err0: if (pos < 0)
- pr_err("Failed to init bootconfig: %s.\n", msg);
- else
- pr_err("Failed to parse %s bootconfig: %s at %zu.\n",
- pos < embeded_size ? "embedded" : "initrd",
- msg, pos < embeded_size ? pos : pos - embeded_size);
+err0: boot_config_pr_err(msg, pos, "initrd");
}

static void __init exit_boot_config(void)
@@ -766,6 +840,11 @@ void __init parse_early_param(void)
if (done)
return;

+#ifdef CONFIG_BOOT_CONFIG_EMBED
+ /* Process early options from boot config */
+ setup_boot_config_early();
+#endif
+
/* All fall through to do_early_param. */
strscpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);
parse_early_options(tmp_cmdline);
diff --git a/lib/bootconfig.c b/lib/bootconfig.c
index 841163ce5313..4048057e3e23 100644
--- a/lib/bootconfig.c
+++ b/lib/bootconfig.c
@@ -45,7 +45,11 @@ char * __init xbc_get_embedded_bootconfig(size_t *size)
* node (for array).
*/

+#ifdef CONFIG_BOOT_CONFIG_EMBED
+static struct xbc_node xbc_nodes[XBC_NODE_MAX];
+#else
static struct xbc_node *xbc_nodes __initdata;
+#endif
static int xbc_node_num __initdata;
static char *xbc_data __initdata;
static bool xbc_data_allocated __initdata;
@@ -914,8 +918,12 @@ void __init xbc_exit(void)
xbc_data_size = 0;
xbc_data_allocated = 0;
xbc_node_num = 0;
+#ifdef CONFIG_BOOT_CONFIG_EMBED
+ memset(xbc_nodes, 0, sizeof(xbc_nodes));
+#else
xbc_free_mem(xbc_nodes, sizeof(struct xbc_node) * XBC_NODE_MAX);
xbc_nodes = NULL;
+#endif
brace_index = 0;
}

@@ -973,6 +981,7 @@ int __init xbc_init(char *data, size_t size, const char **emsg, int *epos)
return -ERANGE;
}

+#ifndef CONFIG_BOOT_CONFIG_EMBED
xbc_nodes = xbc_alloc_mem(sizeof(struct xbc_node) * XBC_NODE_MAX);
if (!xbc_nodes) {
if (emsg)
@@ -980,7 +989,7 @@ int __init xbc_init(char *data, size_t size, const char **emsg, int *epos)
return -ENOMEM;
}
memset(xbc_nodes, 0, sizeof(struct xbc_node) * XBC_NODE_MAX);
-
+#endif
if (!data)
return 0;
xbc_data = data;
@@ -999,6 +1008,7 @@ int __init xbc_append(const char *data, size_t size, const char **emsg, int *epo
{
size_t new_size, parse_start;
char *new_data;
+ int ret;

new_size = xbc_data_size + size;
if (new_size > XBC_DATA_MAX) {
@@ -1024,8 +1034,8 @@ int __init xbc_append(const char *data, size_t size, const char **emsg, int *epo

if (xbc_data_size) {
memcpy(new_data, xbc_data, xbc_data_size - 1);
- new_data[xbc_data_size - 1] = '\n';
parse_start = xbc_data_size - 1;
+ new_data[parse_start] = '\n';
} else {
parse_start = 0;
}
@@ -1039,6 +1049,8 @@ int __init xbc_append(const char *data, size_t size, const char **emsg, int *epo

if (!data)
return 0;
-
- return xbc_parse_and_verify_tree(parse_start, epos, emsg);
+ ret = xbc_parse_and_verify_tree(parse_start, epos, emsg);
+ if (ret && epos)
+ *epos -= parse_start;
+ return ret;
}
--
2.30.2