diff options
Diffstat (limited to 'boot/bootflow.c')
| -rw-r--r-- | boot/bootflow.c | 216 |
1 files changed, 192 insertions, 24 deletions
diff --git a/boot/bootflow.c b/boot/bootflow.c index d79f303486d..d8a4a81a838 100644 --- a/boot/bootflow.c +++ b/boot/bootflow.c @@ -17,6 +17,10 @@ #include <dm/device-internal.h> #include <dm/uclass-internal.h> +/* ensure BOOTMETH_MAX_COUNT fits in method_flags field */ +static_assert(BOOTMETH_MAX_COUNT <= + (sizeof(((struct bootflow_iter *)NULL)->method_flags) * 8)); + /* error codes used to signal running out of things */ enum { BF_NO_MORE_PARTS = -ESHUTDOWN, @@ -109,11 +113,17 @@ int bootflow_iter_drop_bootmeth(struct bootflow_iter *iter, iter->method_order[iter->cur_method] != bmeth) return -EINVAL; + log_debug("Dropping bootmeth '%s'\n", bmeth->name); + memmove(&iter->method_order[iter->cur_method], &iter->method_order[iter->cur_method + 1], (iter->num_methods - iter->cur_method - 1) * sizeof(void *)); iter->num_methods--; + if (iter->first_glob_method > 0) { + iter->first_glob_method--; + log_debug("first_glob_method %d\n", iter->first_glob_method); + } return 0; } @@ -178,6 +188,101 @@ static void scan_next_in_uclass(struct udevice **devp) } /** + * bootmeth_glob_allowed() - Check if a global bootmeth is usable at this point + * + * @iter: Bootflow iterator being used + * Return: true if the global bootmeth has a suitable priority and has not + * already been used + */ +static bool bootmeth_glob_allowed(struct bootflow_iter *iter, int meth_seq) +{ + struct udevice *meth = iter->method_order[meth_seq]; + bool done = iter->methods_done & BIT(meth_seq); + struct bootmeth_uc_plat *ucp; + + ucp = dev_get_uclass_plat(meth); + log_debug("considering glob '%s': done %d glob_prio %d\n", meth->name, + done, ucp->glob_prio); + + /* + * if this one has already been used, or its priority is too low, try + * the next + */ + if (done || ucp->glob_prio > iter->cur_prio) + return false; + + return true; +} + +/** + * next_glob_bootmeth() - Find the next global bootmeth to use + * + * Scans the global bootmeths to find the first unused one whose priority has + * been reached. If found, iter->cur_method and iter->method are set up and + * doing_global is set to true + * + * @iter: Bootflow iterator being used + * Return 0 if found, -ENOENT if no more global bootmeths are available + */ +static int next_glob_bootmeth(struct bootflow_iter *iter) +{ + log_debug("rescan global bootmeths have_global %d\n", + iter->have_global); + if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) && iter->have_global) { + int i; + + /* rescan the global bootmeths */ + log_debug("first_glob_method %d num_methods %d methods_done %x\n", + iter->first_glob_method, iter->num_methods, + iter->methods_done); + for (i = iter->first_glob_method; i < iter->num_methods; i++) { + if (bootmeth_glob_allowed(iter, i)) { + iter->cur_method = i; + iter->method = iter->method_order[i]; + iter->doing_global = true; + iter->dev = NULL; + return 0; + } + } + } + + return -ENOENT; +} + +/** + * prepare_bootdev() - Get ready to use a bootdev + * + * @iter: Bootflow iterator being used + * @dev: UCLASS_BOOTDEV device to use + * @method_flags: Method flag for the bootdev + * @check_global: true to check global bootmeths before processing @dev + * Return 0 if OK, -ve if the bootdev failed to probe + */ +static int prepare_bootdev(struct bootflow_iter *iter, struct udevice *dev, + int method_flags, bool check_global) +{ + int ret; + + if (check_global && !next_glob_bootmeth(iter)) { + iter->pending_bootdev = dev; + iter->pending_method_flags = method_flags; + return 0; + } + + /* + * Probe the bootdev. This does not probe any attached block device, + * since they are siblings + */ + ret = device_probe(dev); + log_debug("probe %s %d\n", dev->name, ret); + if (ret) + return log_msg_ret("probe", ret); + bootflow_iter_set_dev(iter, dev, method_flags); + + return 0; +} + +/** * iter_incr() - Move to the next item (method, part, bootdev) * * Return: 0 if OK, BF_NO_MORE_DEVICES if there are no more bootdevs @@ -195,27 +300,77 @@ static int iter_incr(struct bootflow_iter *iter) if (iter->err == BF_NO_MORE_DEVICES) return BF_NO_MORE_DEVICES; - if (iter->err != BF_NO_MORE_PARTS) { - /* Get the next boothmethod */ - if (++iter->cur_method < iter->num_methods) { + /* Get the next boothmethod */ + for (iter->cur_method++; iter->cur_method < iter->num_methods; + iter->cur_method++) { + /* loop until we find a global bootmeth we haven't used */ + if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) && iter->doing_global) { + if (!bootmeth_glob_allowed(iter, iter->cur_method)) + continue; + iter->method = iter->method_order[iter->cur_method]; + log_debug("-> next global method '%s'\n", + iter->method->name); return 0; } + /* at this point we are only considering non-global bootmeths */ + if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) && iter->have_global && + iter->cur_method >= iter->first_glob_method) + break; + + iter->method = iter->method_order[iter->cur_method]; + return 0; + } + + /* + * If we have finished scanning the global bootmeths, start the + * normal bootdev scan + */ + if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) && global) { + iter->doing_global = false; + /* - * If we have finished scanning the global bootmeths, start the - * normal bootdev scan + * we've come to the end, so see if we should use a pending + * bootdev from when we decided to rescan the global bootmeths */ - if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) && global) { - iter->num_methods = iter->first_glob_method; - iter->doing_global = false; - - /* - * Don't move to the next dev as we haven't tried this - * one yet! - */ - inc_dev = false; + if (iter->pending_bootdev) { + int meth_flags = iter->pending_method_flags; + + dev = iter->pending_bootdev; + iter->pending_bootdev = NULL; + iter->pending_method_flags = 0; + + ret = prepare_bootdev(iter, dev, meth_flags, false); + if (ret) + return log_msg_ret("ipb", ret); + + iter->cur_method = 0; + iter->method = iter->method_order[iter->cur_method]; + + log_debug("-> using pending bootdev '%s' method '%s'\n", + dev->name, iter->method->name); + + return 0; } + + /* if this was the final global bootmeth check, we are done */ + if (iter->cur_prio == BOOTDEVP_COUNT) { + log_debug("-> done global bootmeths\n"); + + /* print the same message as bootflow_iter_set_dev() */ + if ((iter->flags & (BOOTFLOWIF_SHOW | + BOOTFLOWIF_SINGLE_DEV)) == + BOOTFLOWIF_SHOW) + printf("No more bootdevs\n"); + return BF_NO_MORE_DEVICES; + } + + /* + * Don't move to the next dev as we haven't tried this + * one yet! + */ + inc_dev = false; } if (iter->flags & BOOTFLOWIF_SINGLE_PARTITION) @@ -310,17 +465,20 @@ static int iter_incr(struct bootflow_iter *iter) } log_debug("ret=%d, dev=%p %s\n", ret, dev, dev ? dev->name : "none"); - if (ret) { + if (ret) bootflow_iter_set_dev(iter, NULL, 0); - } else { - /* - * Probe the bootdev. This does not probe any attached - * block device, since they are siblings - */ - ret = device_probe(dev); - log_debug("probe %s %d\n", dev->name, ret); - if (!log_msg_ret("probe", ret)) - bootflow_iter_set_dev(iter, dev, method_flags); + else + ret = prepare_bootdev(iter, dev, method_flags, true); + } + + if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) && ret) { + log_debug("no more bootdevs, trying global\n"); + + /* allow global bootmeths with any priority */ + iter->cur_prio = BOOTDEVP_COUNT; + if (!next_glob_bootmeth(iter)) { + log_debug("-> next method '%s'\n", iter->method->name); + return 0; } } @@ -344,6 +502,7 @@ static int bootflow_check(struct bootflow_iter *iter, struct bootflow *bflow) struct udevice *dev; int ret; + /* handle global bootmeths if needed */ if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) && iter->doing_global) { bootflow_iter_set_dev(iter, NULL, 0); ret = bootmeth_get_bootflow(iter->method, bflow); @@ -410,6 +569,10 @@ int bootflow_scan_first(struct udevice *dev, const char *label, bootflow_iter_set_dev(iter, dev, method_flags); } + if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL)) { + iter->methods_done |= BIT(iter->cur_method); + log_debug("methods_done now %x\n", iter->cur_method); + } ret = bootflow_check(iter, bflow); if (ret) { log_debug("check - ret=%d\n", ret); @@ -437,6 +600,11 @@ int bootflow_scan_next(struct bootflow_iter *iter, struct bootflow *bflow) return log_msg_ret("done", ret); if (!ret) { + if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL)) { + iter->methods_done |= BIT(iter->cur_method); + log_debug("methods_done now %x\n", + iter->cur_method); + } ret = bootflow_check(iter, bflow); log_debug("check - ret=%d\n", ret); if (!ret) |
