diff --git a/boot/bootflow.c b/boot/bootflow.c index 26f26aee38a..8c649e8e66c 100644 --- a/boot/bootflow.c +++ b/boot/bootflow.c @@ -801,3 +801,56 @@ int cmdline_set_arg(char *buf, int maxlen, const char *cmdline, return to - buf; } + +int bootflow_cmdline_set_arg(struct bootflow *bflow, const char *set_arg, + const char *new_val, bool set_env) +{ + char buf[2048]; + char *cmd = NULL; + int ret; + + ret = cmdline_set_arg(buf, sizeof(buf), bflow->cmdline, set_arg, + new_val, NULL); + if (ret < 0) + return ret; + + ret = bootflow_cmdline_set(bflow, buf); + if (*buf) { + cmd = strdup(buf); + if (!cmd) + return -ENOMEM; + } + free(bflow->cmdline); + bflow->cmdline = cmd; + + if (set_env) { + ret = env_set("bootargs", bflow->cmdline); + if (ret) + return ret; + } + + return 0; +} + +int cmdline_get_arg(const char *cmdline, const char *arg, int *posp) +{ + int ret; + + ret = cmdline_set_arg(NULL, 1, cmdline, arg, NULL, posp); + + return ret; +} + +int bootflow_cmdline_get_arg(struct bootflow *bflow, const char *arg, + const char **val) +{ + int ret; + int pos; + + ret = cmdline_get_arg(bflow->cmdline, arg, &pos); + if (ret < 0) + return ret; + *val = bflow->cmdline + pos; + + return ret; +} diff --git a/cmd/bootflow.c b/cmd/bootflow.c index bf30087c7c4..ab00e4a19e1 100644 --- a/cmd/bootflow.c +++ b/cmd/bootflow.c @@ -431,6 +431,72 @@ static int do_bootflow_menu(struct cmd_tbl *cmdtp, int flag, int argc, return 0; } + +static int do_bootflow_cmdline(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct bootstd_priv *std; + struct bootflow *bflow; + const char *op, *arg, *val = NULL; + int ret; + + if (argc < 3) + return CMD_RET_USAGE; + + ret = bootstd_get_priv(&std); + if (ret) + return CMD_RET_FAILURE; + + bflow = std->cur_bootflow; + if (!bflow) { + printf("No bootflow selected\n"); + return CMD_RET_FAILURE; + } + + op = argv[1]; + arg = argv[2]; + if (*op == 's') { + if (argc < 4) + return CMD_RET_USAGE; + val = argv[3]; + } + + switch (*op) { + case 'c': /* clear */ + val = ""; + fallthrough; + case 's': /* set */ + case 'd': /* delete */ + ret = bootflow_cmdline_set_arg(bflow, arg, val, true); + break; + case 'g': /* get */ + ret = bootflow_cmdline_get_arg(bflow, arg, &val); + if (ret >= 0) + printf("%.*s\n", ret, val); + break; + } + switch (ret) { + case -E2BIG: + printf("Argument too long\n"); + break; + case -ENOENT: + printf("Argument not found\n"); + break; + case -EINVAL: + printf("Mismatched quotes\n"); + break; + case -EBADF: + printf("Value must be quoted\n"); + break; + default: + if (ret < 0) + printf("Unknown error: %dE\n", ret); + } + if (ret < 0) + return CMD_RET_FAILURE; + + return 0; +} #endif /* CONFIG_CMD_BOOTFLOW_FULL */ #ifdef CONFIG_SYS_LONGHELP @@ -441,7 +507,8 @@ static char bootflow_help_text[] = "bootflow select [|] - select a bootflow\n" "bootflow info [-d] - show info on current bootflow (-d dump bootflow)\n" "bootflow boot - boot current bootflow (or first available if none selected)\n" - "bootflow menu [-t] - show a menu of available bootflows"; + "bootflow menu [-t] - show a menu of available bootflows\n" + "bootflow cmdline [set|get|clear|delete] [] - update cmdline"; #else "scan - boot first available bootflow\n"; #endif @@ -455,5 +522,6 @@ U_BOOT_CMD_WITH_SUBCMDS(bootflow, "Boot flows", bootflow_help_text, U_BOOT_SUBCMD_MKENT(info, 2, 1, do_bootflow_info), U_BOOT_SUBCMD_MKENT(boot, 1, 1, do_bootflow_boot), U_BOOT_SUBCMD_MKENT(menu, 2, 1, do_bootflow_menu), + U_BOOT_SUBCMD_MKENT(cmdline, 4, 1, do_bootflow_cmdline), #endif ); diff --git a/doc/usage/cmd/bootflow.rst b/doc/usage/cmd/bootflow.rst index 907d44ad9f9..07af789e670 100644 --- a/doc/usage/cmd/bootflow.rst +++ b/doc/usage/cmd/bootflow.rst @@ -13,7 +13,7 @@ Synopis bootflow select [] bootflow info [-d] bootflow boot - + bootflow cmdline [set|get|clear|delete] [] Description ----------- @@ -198,6 +198,26 @@ bootflow boot This boots the current bootflow. +bootflow cmdline +~~~~~~~~~~~~~~~~ + +Some bootmeths can obtain the OS command line since it is stored with the OS. +In that case, you can use `bootflow cmdline` to adjust this. The command line +is assumed to be in the format used by Linux, i.e. a space-separated set of +parameters with optional values, e.g. "noinitrd console=/dev/tty0". + +To change or add a parameter, use:: + + bootflow cmdline set + +To clear a parameter value to empty you can use "" for the value, or use:: + + bootflow cmdline clear + +To delete a parameter entirely, use:: + + bootflow cmdline delete + Example ------- diff --git a/include/bootflow.h b/include/bootflow.h index 8db875adf61..7a8595f3dfc 100644 --- a/include/bootflow.h +++ b/include/bootflow.h @@ -484,4 +484,46 @@ int bootflow_menu_run(struct bootstd_priv *std, bool text_mode, int cmdline_set_arg(char *buf, int maxlen, const char *cmdline, const char *set_arg, const char *new_val, int *posp); +/** + * bootflow_cmdline_set_arg() - Set a single argument for a bootflow + * + * Update the allocated cmdline and set the bootargs variable + * + * @bflow: Bootflow to update + * @arg: Argument to update (e.g. "console") + * @val: Value to set (e.g. "ttyS2") or NULL to delete the argument if present, + * "" to set it to an empty value (e.g. "console=") and BOOTFLOWCL_EMPTY to add + * it without any value ("initrd") + * @set_env: true to set the "bootargs" environment variable too + * + * Return: 0 if OK, -ENOMEM if out of memory + */ +int bootflow_cmdline_set_arg(struct bootflow *bflow, const char *arg, + const char *val, bool set_env); + +/** + * cmdline_get_arg() - Read an argument from a cmdline + * + * @cmdline: Command line to read, in the form: + * + * fred mary= jane=123 john="has spaces" + * @arg: Argument to read (may or may not exist) + * @posp: Returns position of argument (after any leading quote) if present + * Return: Length of argument value excluding quotes if found, -ENOENT if not + * found + */ +int cmdline_get_arg(const char *cmdline, const char *arg, int *posp); + +/** + * bootflow_cmdline_get_arg() - Read an argument from a cmdline + * + * @bootflow: Bootflow to read from + * @arg: Argument to read (may or may not exist) + * @valp: Returns a pointer to the argument (after any leading quote) if present + * Return: Length of argument value excluding quotes if found, -ENOENT if not + * found + */ +int bootflow_cmdline_get_arg(struct bootflow *bflow, const char *arg, + const char **val); + #endif diff --git a/test/boot/bootflow.c b/test/boot/bootflow.c index ead71172e12..8a4e090e9bc 100644 --- a/test/boot/bootflow.c +++ b/test/boot/bootflow.c @@ -837,3 +837,111 @@ static int test_bootflow_cmdline_set(struct unit_test_state *uts) return 0; } BOOTSTD_TEST(test_bootflow_cmdline_set, 0); + +/* Test of bootflow_cmdline_set_arg() */ +static int bootflow_set_arg(struct unit_test_state *uts) +{ + struct bootflow s_bflow, *bflow = &s_bflow; + ulong mem_start; + + ut_assertok(env_set("bootargs", NULL)); + + mem_start = ut_check_delta(0); + + /* Do a simple sanity check. Rely on bootflow_cmdline() for the rest */ + bflow->cmdline = NULL; + ut_assertok(bootflow_cmdline_set_arg(bflow, "fred", "123", false)); + ut_asserteq_str(bflow->cmdline, "fred=123"); + + ut_assertok(bootflow_cmdline_set_arg(bflow, "mary", "and here", false)); + ut_asserteq_str(bflow->cmdline, "fred=123 mary=\"and here\""); + + ut_assertok(bootflow_cmdline_set_arg(bflow, "mary", NULL, false)); + ut_asserteq_str(bflow->cmdline, "fred=123"); + ut_assertok(bootflow_cmdline_set_arg(bflow, "fred", NULL, false)); + ut_asserteq_ptr(bflow->cmdline, NULL); + + ut_asserteq(0, ut_check_delta(mem_start)); + + ut_assertok(bootflow_cmdline_set_arg(bflow, "mary", "here", true)); + ut_asserteq_str("mary=here", env_get("bootargs")); + ut_assertok(env_set("bootargs", NULL)); + + return 0; +} +BOOTSTD_TEST(bootflow_set_arg, 0); + +/* Test of bootflow_cmdline_get_arg() */ +static int bootflow_cmdline_get(struct unit_test_state *uts) +{ + int pos; + + /* empty string */ + ut_asserteq(-ENOENT, cmdline_get_arg("", "fred", &pos)); + + /* arg with empty value */ + ut_asserteq(0, cmdline_get_arg("fred= mary", "fred", &pos)); + ut_asserteq(5, pos); + + /* arg with a value */ + ut_asserteq(2, cmdline_get_arg("fred=23", "fred", &pos)); + ut_asserteq(5, pos); + + /* arg with a value */ + ut_asserteq(3, cmdline_get_arg("mary=1 fred=234", "fred", &pos)); + ut_asserteq(12, pos); + + /* arg with a value, after quoted arg */ + ut_asserteq(3, cmdline_get_arg("mary=\"1 2\" fred=234", "fred", &pos)); + ut_asserteq(16, pos); + + /* arg in the middle */ + ut_asserteq(0, cmdline_get_arg("mary=\"1 2\" fred john=23", "fred", + &pos)); + ut_asserteq(15, pos); + + /* quoted arg */ + ut_asserteq(3, cmdline_get_arg("mary=\"1 2\" fred=\"3 4\" john=23", + "fred", &pos)); + ut_asserteq(17, pos); + + /* args starting with the same prefix */ + ut_asserteq(1, cmdline_get_arg("mary=abc johnathon=3 john=1", "john", + &pos)); + ut_asserteq(26, pos); + + return 0; +} +BOOTSTD_TEST(bootflow_cmdline_get, 0); + +static int bootflow_cmdline(struct unit_test_state *uts) +{ + ut_assertok(run_command("bootflow scan mmc", 0)); + ut_assertok(run_command("bootflow sel 0", 0)); + console_record_reset_enable(); + + ut_asserteq(1, run_command("bootflow cmdline get fred", 0)); + ut_assert_nextline("Argument not found"); + ut_assert_console_end(); + + ut_asserteq(0, run_command("bootflow cmdline set fred 123", 0)); + ut_asserteq(0, run_command("bootflow cmdline get fred", 0)); + ut_assert_nextline("123"); + + ut_asserteq(0, run_command("bootflow cmdline set mary abc", 0)); + ut_asserteq(0, run_command("bootflow cmdline get mary", 0)); + ut_assert_nextline("abc"); + + ut_asserteq(0, run_command("bootflow cmdline delete fred", 0)); + ut_asserteq(1, run_command("bootflow cmdline get fred", 0)); + ut_assert_nextline("Argument not found"); + + ut_asserteq(0, run_command("bootflow cmdline clear mary", 0)); + ut_asserteq(0, run_command("bootflow cmdline get mary", 0)); + ut_assert_nextline_empty(); + + ut_assert_console_end(); + + return 0; +} +BOOTSTD_TEST(bootflow_cmdline, 0);