diff options
Diffstat (limited to 'boot/bootflow.c')
-rw-r--r-- | boot/bootflow.c | 339 |
1 files changed, 337 insertions, 2 deletions
diff --git a/boot/bootflow.c b/boot/bootflow.c index 8f2cb876bb4..81b5829d5b3 100644 --- a/boot/bootflow.c +++ b/boot/bootflow.c @@ -12,7 +12,9 @@ #include <bootmeth.h> #include <bootstd.h> #include <dm.h> +#include <env_internal.h> #include <malloc.h> +#include <serial.h> #include <dm/device-internal.h> #include <dm/uclass-internal.h> @@ -315,14 +317,14 @@ static int bootflow_check(struct bootflow_iter *iter, struct bootflow *bflow) /* If we got a valid bootflow, return it */ if (!ret) { - log_debug("Bootdevice '%s' part %d method '%s': Found bootflow\n", + log_debug("Bootdev '%s' part %d method '%s': Found bootflow\n", dev->name, iter->part, iter->method->name); return 0; } /* Unless there is nothing more to try, move to the next device */ else if (ret != BF_NO_MORE_PARTS && ret != -ENOSYS) { - log_debug("Bootdevice '%s' part %d method '%s': Error %d\n", + log_debug("Bootdev '%s' part %d method '%s': Error %d\n", dev->name, iter->part, iter->method->name, ret); /* * For 'all' we return all bootflows, even @@ -552,3 +554,336 @@ int bootflow_iter_check_system(const struct bootflow_iter *iter) return -ENOTSUPP; } + +/** + * bootflow_cmdline_set() - Set the command line for a bootflow + * + * @value: New command-line string + * Returns 0 if OK, -ENOENT if no current bootflow, -ENOMEM if out of memory + */ +int bootflow_cmdline_set(struct bootflow *bflow, const char *value) +{ + char *cmdline = NULL; + + if (value) { + cmdline = strdup(value); + if (!cmdline) + return -ENOMEM; + } + + free(bflow->cmdline); + bflow->cmdline = cmdline; + + return 0; +} + +#ifdef CONFIG_BOOTSTD_FULL +/** + * on_bootargs() - Update the cmdline of a bootflow + */ +static int on_bootargs(const char *name, const char *value, enum env_op op, + int flags) +{ + struct bootstd_priv *std; + struct bootflow *bflow; + int ret; + + ret = bootstd_get_priv(&std); + if (ret) + return 0; + bflow = std->cur_bootflow; + if (!bflow) + return 0; + + switch (op) { + case env_op_create: + case env_op_overwrite: + ret = bootflow_cmdline_set(bflow, value); + if (ret && ret != ENOENT) + return 1; + return 0; + case env_op_delete: + bootflow_cmdline_set(bflow, NULL); + fallthrough; + default: + return 0; + } +} +U_BOOT_ENV_CALLBACK(bootargs, on_bootargs); +#endif + +/** + * copy_in() - Copy a string into a cmdline buffer + * + * @buf: Buffer to copy into + * @end: End of buffer (pointer to char after the end) + * @arg: String to copy from + * @len: Number of chars to copy from @arg (note that this is not usually the + * sane as strlen(arg) since the string may contain following arguments) + * @new_val: Value to put after arg, or BOOTFLOWCL_EMPTY to use an empty value + * with no '=' sign + * Returns: Number of chars written to @buf + */ +static int copy_in(char *buf, char *end, const char *arg, int len, + const char *new_val) +{ + char *to = buf; + + /* copy the arg name */ + if (to + len >= end) + return -E2BIG; + memcpy(to, arg, len); + to += len; + + if (new_val == BOOTFLOWCL_EMPTY) { + /* no value */ + } else { + bool need_quote = strchr(new_val, ' '); + len = strlen(new_val); + + /* need space for value, equals sign and maybe two quotes */ + if (to + 1 + (need_quote ? 2 : 0) + len >= end) + return -E2BIG; + *to++ = '='; + if (need_quote) + *to++ = '"'; + memcpy(to, new_val, len); + to += len; + if (need_quote) + *to++ = '"'; + } + + return to - buf; +} + +int cmdline_set_arg(char *buf, int maxlen, const char *cmdline, + const char *set_arg, const char *new_val, int *posp) +{ + bool found_arg = false; + const char *from; + char *to, *end; + int set_arg_len; + char empty = '\0'; + int ret; + + from = cmdline ?: ∅ + + /* check if the value has quotes inside */ + if (new_val && new_val != BOOTFLOWCL_EMPTY && strchr(new_val, '"')) + return -EBADF; + + set_arg_len = strlen(set_arg); + for (to = buf, end = buf + maxlen; *from;) { + const char *val, *arg_end, *val_end, *p; + bool in_quote; + + if (to >= end) + return -E2BIG; + while (*from == ' ') + from++; + if (!*from) + break; + + /* find the end of this arg */ + val = NULL; + arg_end = NULL; + val_end = NULL; + in_quote = false; + for (p = from;; p++) { + if (in_quote) { + if (!*p) + return -EINVAL; + if (*p == '"') + in_quote = false; + continue; + } + if (*p == '=') { + arg_end = p; + val = p + 1; + } else if (*p == '"') { + in_quote = true; + } else if (!*p || *p == ' ') { + val_end = p; + if (!arg_end) + arg_end = p; + break; + } + } + /* + * At this point val_end points to the end of the value, or the + * last char after the arg name, if there is no label. + * arg_end is the char after the arg name + * val points to the value, or NULL if there is none + * char after the value. + * + * fred=1234 + * ^ ^^ ^ + * from || | + * / \ \ + * arg_end val val_end + */ + log_debug("from %s arg_end %ld val %ld val_end %ld\n", from, + (long)(arg_end - from), (long)(val - from), + (long)(val_end - from)); + + if (to != buf) { + if (to >= end) + return -E2BIG; + *to++ = ' '; + } + + /* if this is the target arg, update it */ + if (!strncmp(from, set_arg, arg_end - from)) { + if (!buf) { + bool has_quote = val_end[-1] == '"'; + + /* + * exclude any start/end quotes from + * calculations + */ + if (!val) + val = val_end; + *posp = val - cmdline + has_quote; + return val_end - val - 2 * has_quote; + } + found_arg = true; + if (!new_val) { + /* delete this arg */ + from = val_end + (*val_end == ' '); + log_debug("delete from: %s\n", from); + if (to != buf) + to--; /* drop the space we added */ + continue; + } + + ret = copy_in(to, end, from, arg_end - from, new_val); + if (ret < 0) + return ret; + to += ret; + + /* if not the target arg, copy it unchanged */ + } else if (to) { + int len; + + len = val_end - from; + if (to + len >= end) + return -E2BIG; + memcpy(to, from, len); + to += len; + } + from = val_end; + } + + /* If we didn't find the arg, add it */ + if (!found_arg) { + /* trying to delete something that is not there */ + if (!new_val || !buf) + return -ENOENT; + if (to >= end) + return -E2BIG; + + /* add a space to separate it from the previous arg */ + if (to != buf && to[-1] != ' ') + *to++ = ' '; + ret = copy_in(to, end, set_arg, set_arg_len, new_val); + log_debug("ret=%d, to: %s buf: %s\n", ret, to, buf); + if (ret < 0) + return ret; + to += ret; + } + + /* delete any trailing space */ + if (to > buf && to[-1] == ' ') + to--; + + if (to >= end) + return -E2BIG; + *to++ = '\0'; + + 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; +} + +int bootflow_cmdline_auto(struct bootflow *bflow, const char *arg) +{ + struct serial_device_info info; + char buf[50]; + int ret; + + ret = serial_getinfo(gd->cur_serial_dev, &info); + if (ret) + return ret; + + *buf = '\0'; + if (!strcmp("earlycon", arg)) { + snprintf(buf, sizeof(buf), + "uart8250,mmio32,%#lx,%dn8", info.addr, + info.baudrate); + } else if (!strcmp("console", arg)) { + snprintf(buf, sizeof(buf), + "ttyS0,%dn8", info.baudrate); + } + + if (!*buf) { + printf("Unknown param '%s\n", arg); + return -ENOENT; + } + + ret = bootflow_cmdline_set_arg(bflow, arg, buf, true); + if (ret) + return ret; + + return 0; +} |