diff options
Diffstat (limited to 'boot/bootdev-uclass.c')
-rw-r--r-- | boot/bootdev-uclass.c | 981 |
1 files changed, 981 insertions, 0 deletions
diff --git a/boot/bootdev-uclass.c b/boot/bootdev-uclass.c new file mode 100644 index 00000000000..7c7bba088c9 --- /dev/null +++ b/boot/bootdev-uclass.c @@ -0,0 +1,981 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2021 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include <dm.h> +#include <bootdev.h> +#include <bootflow.h> +#include <bootmeth.h> +#include <bootstd.h> +#include <fs.h> +#include <log.h> +#include <malloc.h> +#include <part.h> +#include <sort.h> +#include <dm/device-internal.h> +#include <dm/lists.h> +#include <dm/uclass-internal.h> + +enum { + /* + * Set some sort of limit on the number of partitions a bootdev can + * have. Note that for disks this limits the partitions numbers that + * are scanned to 1..MAX_BOOTFLOWS_PER_BOOTDEV + */ + MAX_PART_PER_BOOTDEV = 30, + + /* Maximum supported length of the "boot_targets" env string */ + BOOT_TARGETS_MAX_LEN = 100, +}; + +int bootdev_add_bootflow(struct bootflow *bflow) +{ + struct bootstd_priv *std; + struct bootflow *new; + int ret; + + ret = bootstd_get_priv(&std); + if (ret) + return ret; + + new = malloc(sizeof(*bflow)); + if (!new) + return log_msg_ret("bflow", -ENOMEM); + memcpy(new, bflow, sizeof(*bflow)); + + list_add_tail(&new->glob_node, &std->glob_head); + if (bflow->dev) { + struct bootdev_uc_plat *ucp = dev_get_uclass_plat(bflow->dev); + + list_add_tail(&new->bm_node, &ucp->bootflow_head); + } + + return 0; +} + +int bootdev_first_bootflow(struct udevice *dev, struct bootflow **bflowp) +{ + struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev); + + if (list_empty(&ucp->bootflow_head)) + return -ENOENT; + + *bflowp = list_first_entry(&ucp->bootflow_head, struct bootflow, + bm_node); + + return 0; +} + +int bootdev_next_bootflow(struct bootflow **bflowp) +{ + struct bootflow *bflow = *bflowp; + struct bootdev_uc_plat *ucp = dev_get_uclass_plat(bflow->dev); + + *bflowp = NULL; + + if (list_is_last(&bflow->bm_node, &ucp->bootflow_head)) + return -ENOENT; + + *bflowp = list_entry(bflow->bm_node.next, struct bootflow, bm_node); + + return 0; +} + +int bootdev_bind(struct udevice *parent, const char *drv_name, const char *name, + struct udevice **devp) +{ + struct udevice *dev; + char dev_name[30]; + char *str; + int ret; + + snprintf(dev_name, sizeof(dev_name), "%s.%s", parent->name, name); + str = strdup(dev_name); + if (!str) + return -ENOMEM; + ret = device_bind_driver(parent, drv_name, str, &dev); + if (ret) + return ret; + device_set_name_alloced(dev); + *devp = dev; + + return 0; +} + +int bootdev_find_in_blk(struct udevice *dev, struct udevice *blk, + struct bootflow_iter *iter, struct bootflow *bflow) +{ + struct bootmeth_uc_plat *plat = dev_get_uclass_plat(bflow->method); + bool allow_any_part = plat->flags & BOOTMETHF_ANY_PART; + struct blk_desc *desc = dev_get_uclass_plat(blk); + struct disk_partition info; + char partstr[20]; + char name[60]; + int ret; + + /* Sanity check */ + if (iter->part >= MAX_PART_PER_BOOTDEV) + return log_msg_ret("max", -ESHUTDOWN); + + bflow->blk = blk; + if (iter->part) + snprintf(partstr, sizeof(partstr), "part_%x", iter->part); + else + strcpy(partstr, "whole"); + snprintf(name, sizeof(name), "%s.%s", dev->name, partstr); + bflow->name = strdup(name); + if (!bflow->name) + return log_msg_ret("name", -ENOMEM); + + bflow->part = iter->part; + + ret = bootmeth_check(bflow->method, iter); + if (ret) + return log_msg_ret("check", ret); + + /* + * partition numbers start at 0 so this cannot succeed, but it can tell + * us whether there is valid media there + */ + ret = part_get_info(desc, iter->part, &info); + log_debug("part_get_info() returned %d\n", ret); + if (!iter->part && ret == -ENOENT) + ret = 0; + + /* + * This error indicates the media is not present. Otherwise we just + * blindly scan the next partition. We could be more intelligent here + * and check which partition numbers actually exist. + */ + if (ret == -EOPNOTSUPP) + ret = -ESHUTDOWN; + else + bflow->state = BOOTFLOWST_MEDIA; + if (ret && !allow_any_part) { + /* allow partition 1 to be missing */ + if (iter->part == 1) { + iter->max_part = 3; + ret = -ENOENT; + } + + return log_msg_ret("part", ret); + } + + /* + * Currently we don't get the number of partitions, so just + * assume a large number + */ + iter->max_part = MAX_PART_PER_BOOTDEV; + + if (iter->flags & BOOTFLOWIF_SINGLE_PARTITION) { + /* a particular partition was specified, scan it without checking */ + } else if (!iter->part) { + /* This is the whole disk, check if we have bootable partitions */ + iter->first_bootable = part_get_bootable(desc); + log_debug("checking bootable=%d\n", iter->first_bootable); + } else if (allow_any_part) { + /* + * allow any partition to be scanned, by skipping any checks + * for filesystems or partition contents on this disk + */ + + /* if there are bootable partitions, scan only those */ + } else if (iter->first_bootable >= 0 && + (iter->first_bootable ? !info.bootable : iter->part != 1)) { + return log_msg_ret("boot", -EINVAL); + } else { + ret = fs_set_blk_dev_with_part(desc, bflow->part); + bflow->state = BOOTFLOWST_PART; + if (ret) + return log_msg_ret("fs", ret); + + log_debug("%s: Found partition %x type %x fstype %d\n", + blk->name, bflow->part, + IS_ENABLED(CONFIG_DOS_PARTITION) ? + disk_partition_sys_ind(&info) : 0, + ret ? -1 : fs_get_type()); + bflow->blk = blk; + bflow->state = BOOTFLOWST_FS; + } + + log_debug("method %s\n", bflow->method->name); + ret = bootmeth_read_bootflow(bflow->method, bflow); + if (ret) + return log_msg_ret("method", ret); + + return 0; +} + +void bootdev_list(bool probe) +{ + struct udevice *dev; + int ret; + int i; + + printf("Seq Probed Status Uclass Name\n"); + printf("--- ------ ------ -------- ------------------\n"); + if (probe) + ret = uclass_first_device_check(UCLASS_BOOTDEV, &dev); + else + ret = uclass_find_first_device(UCLASS_BOOTDEV, &dev); + for (i = 0; dev; i++) { + printf("%3x [ %c ] %6s %-9.9s %s\n", dev_seq(dev), + device_active(dev) ? '+' : ' ', + ret ? simple_itoa(-ret) : "OK", + dev_get_uclass_name(dev_get_parent(dev)), dev->name); + if (probe) + ret = uclass_next_device_check(&dev); + else + ret = uclass_find_next_device(&dev); + } + printf("--- ------ ------ -------- ------------------\n"); + printf("(%d bootdev%s)\n", i, i != 1 ? "s" : ""); +} + +int bootdev_setup_for_dev(struct udevice *parent, const char *drv_name) +{ + struct udevice *bdev; + int ret; + + ret = device_find_first_child_by_uclass(parent, UCLASS_BOOTDEV, + &bdev); + if (ret) { + if (ret != -ENODEV) { + log_debug("Cannot access bootdev device\n"); + return ret; + } + + ret = bootdev_bind(parent, drv_name, "bootdev", &bdev); + if (ret) { + log_debug("Cannot create bootdev device\n"); + return ret; + } + } + + return 0; +} + +static int bootdev_get_suffix_start(struct udevice *dev, const char *suffix) +{ + int len, slen; + + len = strlen(dev->name); + slen = strlen(suffix); + if (len > slen && !strcmp(suffix, dev->name + len - slen)) + return len - slen; + + return len; +} + +int bootdev_setup_for_sibling_blk(struct udevice *blk, const char *drv_name) +{ + struct udevice *parent, *dev; + char dev_name[50]; + int ret, len; + + len = bootdev_get_suffix_start(blk, ".blk"); + snprintf(dev_name, sizeof(dev_name), "%.*s.%s", len, blk->name, + "bootdev"); + + parent = dev_get_parent(blk); + ret = device_find_child_by_name(parent, dev_name, &dev); + if (ret) { + char *str; + + if (ret != -ENODEV) { + log_debug("Cannot access bootdev device\n"); + return ret; + } + str = strdup(dev_name); + if (!str) + return -ENOMEM; + + ret = device_bind_driver(parent, drv_name, str, &dev); + if (ret) { + log_debug("Cannot create bootdev device\n"); + return ret; + } + device_set_name_alloced(dev); + } + + return 0; +} + +int bootdev_get_sibling_blk(struct udevice *dev, struct udevice **blkp) +{ + struct udevice *parent = dev_get_parent(dev); + struct udevice *blk; + int ret, len; + + if (device_get_uclass_id(dev) != UCLASS_BOOTDEV) + return -EINVAL; + + /* + * This should always work if bootdev_setup_for_sibling_blk() was used + */ + len = bootdev_get_suffix_start(dev, ".bootdev"); + ret = device_find_child_by_namelen(parent, dev->name, len, &blk); + if (ret) { + char dev_name[50]; + + snprintf(dev_name, sizeof(dev_name), "%.*s.blk", len, + dev->name); + ret = device_find_child_by_name(parent, dev_name, &blk); + if (ret) + return log_msg_ret("find", ret); + } + ret = device_probe(blk); + if (ret) + return log_msg_ret("act", ret); + *blkp = blk; + + return 0; +} + +static int bootdev_get_from_blk(struct udevice *blk, struct udevice **bootdevp) +{ + struct udevice *parent = dev_get_parent(blk); + struct udevice *bootdev; + char dev_name[50]; + int ret, len; + + if (device_get_uclass_id(blk) != UCLASS_BLK) + return -EINVAL; + + /* This should always work if bootdev_setup_for_sibling_blk() was used */ + len = bootdev_get_suffix_start(blk, ".blk"); + snprintf(dev_name, sizeof(dev_name), "%.*s.%s", len, blk->name, + "bootdev"); + ret = device_find_child_by_name(parent, dev_name, &bootdev); + if (ret) + return log_msg_ret("find", ret); + *bootdevp = bootdev; + + return 0; +} + +int bootdev_unbind_dev(struct udevice *parent) +{ + struct udevice *dev; + int ret; + + ret = device_find_first_child_by_uclass(parent, UCLASS_BOOTDEV, &dev); + if (!ret) { + ret = device_remove(dev, DM_REMOVE_NORMAL); + if (ret) + return log_msg_ret("rem", ret); + ret = device_unbind(dev); + if (ret) + return log_msg_ret("unb", ret); + } + + return 0; +} + +/** + * label_to_uclass() - Convert a label to a uclass and sequence number + * + * @label: Label to look up (e.g. "mmc1" or "mmc0") + * @seqp: Returns the sequence number, or -1 if none + * @method_flagsp: If non-NULL, returns any flags implied by the label + * (enum bootflow_meth_flags_t), 0 if none + * Returns: sequence number on success, -EPFNOSUPPORT is the uclass is not + * known, other -ve error code on other error + */ +static int label_to_uclass(const char *label, int *seqp, int *method_flagsp) +{ + int seq, len, method_flags; + enum uclass_id id; + const char *end; + + method_flags = 0; + seq = trailing_strtoln_end(label, NULL, &end); + len = end - label; + if (!len) + return -EINVAL; + id = uclass_get_by_namelen(label, len); + log_debug("find %s: seq=%d, id=%d/%s\n", label, seq, id, + uclass_get_name(id)); + if (id == UCLASS_INVALID) { + /* try some special cases */ + if (IS_ENABLED(CONFIG_BOOTDEV_SPI_FLASH) && + !strncmp("spi", label, len)) { + id = UCLASS_SPI_FLASH; + } else if (IS_ENABLED(CONFIG_BOOTDEV_ETH) && + !strncmp("pxe", label, len)) { + id = UCLASS_ETH; + method_flags |= BOOTFLOW_METHF_PXE_ONLY; + } else if (IS_ENABLED(CONFIG_BOOTDEV_ETH) && + !strncmp("dhcp", label, len)) { + id = UCLASS_ETH; + method_flags |= BOOTFLOW_METHF_DHCP_ONLY; + } else { + return -EPFNOSUPPORT; + } + } + if (id == UCLASS_USB) + id = UCLASS_MASS_STORAGE; + *seqp = seq; + if (method_flagsp) + *method_flagsp = method_flags; + + return id; +} + +int bootdev_find_by_label(const char *label, struct udevice **devp, + int *method_flagsp) +{ + int seq, ret, method_flags = 0; + struct udevice *media; + struct uclass *uc; + enum uclass_id id; + + ret = label_to_uclass(label, &seq, &method_flags); + if (ret < 0) + return log_msg_ret("uc", ret); + id = ret; + + /* Iterate through devices in the media uclass (e.g. UCLASS_MMC) */ + uclass_id_foreach_dev(id, media, uc) { + struct udevice *bdev, *blk; + int ret; + + /* if there is no seq, match anything */ + if (seq != -1 && dev_seq(media) != seq) { + log_debug("- skip, media seq=%d\n", dev_seq(media)); + continue; + } + + ret = device_find_first_child_by_uclass(media, UCLASS_BOOTDEV, + &bdev); + if (ret) { + log_debug("- looking via blk, seq=%d, id=%d\n", seq, + id); + ret = blk_find_device(id, seq, &blk); + if (!ret) { + log_debug("- get from blk %s\n", blk->name); + ret = bootdev_get_from_blk(blk, &bdev); + } + } + if (!ret) { + log_debug("- found %s\n", bdev->name); + *devp = bdev; + + /* + * if no sequence number was provided, we must scan all + * bootdevs for this media uclass + */ + if (seq == -1) + method_flags |= BOOTFLOW_METHF_SINGLE_UCLASS; + if (method_flagsp) + *method_flagsp = method_flags; + log_debug("method flags %x\n", method_flags); + return 0; + } + log_debug("- no device in %s\n", media->name); + } + + return -ENOENT; +} + +int bootdev_find_by_any(const char *name, struct udevice **devp, + int *method_flagsp) +{ + struct udevice *dev; + int method_flags = 0; + int ret = -ENODEV, seq; + char *endp; + + seq = simple_strtol(name, &endp, 16); + + /* Select by name, label or number */ + if (*endp) { + ret = uclass_get_device_by_name(UCLASS_BOOTDEV, name, &dev); + if (ret == -ENODEV) { + ret = bootdev_find_by_label(name, &dev, &method_flags); + if (ret) { + printf("Cannot find bootdev '%s' (err=%d)\n", + name, ret); + return log_msg_ret("lab", ret); + } + ret = device_probe(dev); + } + if (ret) { + printf("Cannot probe bootdev '%s' (err=%d)\n", name, + ret); + return log_msg_ret("pro", ret); + } + } else if (IS_ENABLED(CONFIG_BOOTSTD_FULL)) { + ret = uclass_get_device_by_seq(UCLASS_BOOTDEV, seq, &dev); + method_flags |= BOOTFLOW_METHF_SINGLE_DEV; + } + if (ret) { + printf("Cannot find '%s' (err=%d)\n", name, ret); + return ret; + } + + *devp = dev; + if (method_flagsp) + *method_flagsp = method_flags; + + return 0; +} + +int bootdev_hunt_and_find_by_label(const char *label, struct udevice **devp, + int *method_flagsp) +{ + int ret; + + ret = bootdev_hunt(label, false); + if (ret) + return log_msg_ret("scn", ret); + ret = bootdev_find_by_label(label, devp, method_flagsp); + if (ret) + return log_msg_ret("fnd", ret); + + return 0; +} + +static int default_get_bootflow(struct udevice *dev, struct bootflow_iter *iter, + struct bootflow *bflow) +{ + struct udevice *blk; + int ret; + + ret = bootdev_get_sibling_blk(dev, &blk); + log_debug("sibling_blk ret=%d, blk=%s\n", ret, + ret ? "(none)" : blk->name); + /* + * If there is no media, indicate that no more partitions should be + * checked + */ + if (ret == -EOPNOTSUPP) + ret = -ESHUTDOWN; + if (ret) + return log_msg_ret("blk", ret); + assert(blk); + ret = bootdev_find_in_blk(dev, blk, iter, bflow); + if (ret) + return log_msg_ret("find", ret); + + return 0; +} + +int bootdev_get_bootflow(struct udevice *dev, struct bootflow_iter *iter, + struct bootflow *bflow) +{ + const struct bootdev_ops *ops = bootdev_get_ops(dev); + + log_debug("->get_bootflow %s,%x=%p\n", dev->name, iter->part, + ops->get_bootflow); + bootflow_init(bflow, dev, iter->method); + if (!ops->get_bootflow) + return default_get_bootflow(dev, iter, bflow); + + return ops->get_bootflow(dev, iter, bflow); +} + +void bootdev_clear_bootflows(struct udevice *dev) +{ + struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev); + + while (!list_empty(&ucp->bootflow_head)) { + struct bootflow *bflow; + + bflow = list_first_entry(&ucp->bootflow_head, struct bootflow, + bm_node); + bootflow_remove(bflow); + } +} + +int bootdev_next_label(struct bootflow_iter *iter, struct udevice **devp, + int *method_flagsp) +{ + struct udevice *dev; + + log_debug("next\n"); + for (dev = NULL; !dev && iter->labels[++iter->cur_label];) { + const char *label = iter->labels[iter->cur_label]; + int ret; + + log_debug("Scanning: %s\n", label); + ret = bootdev_hunt_and_find_by_label(label, &dev, + method_flagsp); + if (iter->flags & BOOTFLOWIF_SHOW) { + if (ret == -EPFNOSUPPORT) { + log_warning("Unknown uclass '%s' in label\n", + label); + } else if (ret == -ENOENT) { + /* + * looking for, e.g. 'scsi0' should find + * something if SCSI is present + */ + if (!trailing_strtol(label)) { + log_warning("No bootdevs for '%s'\n", + label); + } + } + } + + } + + if (!dev) + return log_msg_ret("fin", -ENODEV); + *devp = dev; + + return 0; +} + +int bootdev_next_prio(struct bootflow_iter *iter, struct udevice **devp) +{ + struct udevice *dev = *devp, *last_dev = NULL; + bool found; + int ret; + + /* find the next device with this priority */ + *devp = NULL; + log_debug("next prio %d: dev=%p/%s\n", iter->cur_prio, dev, + dev ? dev->name : "none"); + do { + /* + * Don't probe devices here since they may not be of the + * required priority + */ + if (!dev) + uclass_find_first_device(UCLASS_BOOTDEV, &dev); + else + uclass_find_next_device(&dev); + found = false; + + /* scan for the next device with the correct priority */ + while (dev) { + struct bootdev_uc_plat *plat; + + plat = dev_get_uclass_plat(dev); + log_debug("- %s: %d, want %d\n", dev->name, plat->prio, + iter->cur_prio); + if (plat->prio == iter->cur_prio) + break; + uclass_find_next_device(&dev); + } + + /* none found for this priority, so move to the next */ + if (!dev) { + log_debug("None found at prio %d, moving to %d\n", + iter->cur_prio, iter->cur_prio + 1); + if (++iter->cur_prio == BOOTDEVP_COUNT) + return log_msg_ret("fin", -ENODEV); + + if (iter->flags & BOOTFLOWIF_HUNT) { + /* hunt to find new bootdevs */ + ret = bootdev_hunt_prio(iter->cur_prio, + iter->flags & + BOOTFLOWIF_SHOW); + log_debug("- bootdev_hunt_prio() ret %d\n", + ret); + if (ret) + return log_msg_ret("hun", ret); + } + } else { + ret = device_probe(dev); + if (!ret) + last_dev = dev; + if (ret) { + log_warning("Device '%s' failed to probe\n", + dev->name); + if (last_dev == dev) { + /* + * We have already tried this device + * and it failed to probe. Give up. + */ + return log_msg_ret("probe", ret); + } + last_dev = dev; + dev = NULL; + } + } + } while (!dev); + + *devp = dev; + + return 0; +} + +int bootdev_setup_iter(struct bootflow_iter *iter, const char *label, + struct udevice **devp, int *method_flagsp) +{ + struct udevice *bootstd, *dev = NULL; + bool show = iter->flags & BOOTFLOWIF_SHOW; + int method_flags; + char buf[32]; + int ret; + + if (label) { + const char *end = strchr(label, ':'); + + if (end) { + size_t len = (size_t)(end - label); + const char *part = end + 1; + + if (len + 1 > sizeof(buf)) { + log_err("label \"%s\" is way too long\n", label); + return -EINVAL; + } + + memcpy(buf, label, len); + buf[len] = '\0'; + label = buf; + + unsigned long tmp; + + if (strict_strtoul(part, 0, &tmp)) { + log_err("Invalid partition number: %s\n", part); + return -EINVAL; + } + + iter->flags |= BOOTFLOWIF_SINGLE_PARTITION; + iter->part = tmp; + } + } + + ret = uclass_first_device_err(UCLASS_BOOTSTD, &bootstd); + if (ret) { + log_err("Missing bootstd device\n"); + return log_msg_ret("std", ret); + } + + /* hunt for any pre-scan devices */ + if (iter->flags & BOOTFLOWIF_HUNT) { + ret = bootdev_hunt_prio(BOOTDEVP_1_PRE_SCAN, show); + log_debug("- bootdev_hunt_prio() ret %d\n", ret); + if (ret) + return log_msg_ret("pre", ret); + } + + /* Handle scanning a single device */ + if (IS_ENABLED(CONFIG_BOOTSTD_FULL) && label) { + if (iter->flags & BOOTFLOWIF_HUNT) { + ret = bootdev_hunt(label, show); + if (ret) + return log_msg_ret("hun", ret); + } + ret = bootdev_find_by_any(label, &dev, &method_flags); + if (ret) + return log_msg_ret("lab", ret); + + log_debug("method_flags: %x\n", method_flags); + if (method_flags & BOOTFLOW_METHF_SINGLE_UCLASS) + iter->flags |= BOOTFLOWIF_SINGLE_UCLASS; + else if (method_flags & BOOTFLOW_METHF_SINGLE_DEV) + iter->flags |= BOOTFLOWIF_SINGLE_DEV; + else + iter->flags |= BOOTFLOWIF_SINGLE_MEDIA; + log_debug("Selected label: %s, flags %x\n", label, iter->flags); + } else { + bool ok; + + /* This either returns a non-empty list or NULL */ + iter->labels = bootstd_get_bootdev_order(bootstd, &ok); + if (!ok) + return log_msg_ret("ord", -ENOMEM); + log_debug("setup labels %p\n", iter->labels); + if (iter->labels) { + iter->cur_label = -1; + ret = bootdev_next_label(iter, &dev, &method_flags); + } else { + ret = bootdev_next_prio(iter, &dev); + method_flags = 0; + } + if (!dev) + return log_msg_ret("fin", -ENOENT); + log_debug("Selected bootdev: %s\n", dev->name); + } + + ret = device_probe(dev); + if (ret) + return log_msg_ret("probe", ret); + if (method_flagsp) + *method_flagsp = method_flags; + *devp = dev; + + return 0; +} + +static int bootdev_hunt_drv(struct bootdev_hunter *info, uint seq, bool show) +{ + const char *name = uclass_get_name(info->uclass); + struct bootstd_priv *std; + int ret; + + ret = bootstd_get_priv(&std); + if (ret) + return log_msg_ret("std", ret); + + if (!(std->hunters_used & BIT(seq))) { + if (show) + printf("Hunting with: %s\n", + uclass_get_name(info->uclass)); + log_debug("Hunting with: %s\n", name); + if (info->hunt) { + ret = info->hunt(info, show); + log_debug(" - hunt result %d\n", ret); + if (ret && ret != -ENOENT) + return ret; + } + std->hunters_used |= BIT(seq); + } + + return 0; +} + +int bootdev_hunt(const char *spec, bool show) +{ + struct bootdev_hunter *start; + const char *end; + int n_ent, i; + int result; + size_t len; + + start = ll_entry_start(struct bootdev_hunter, bootdev_hunter); + n_ent = ll_entry_count(struct bootdev_hunter, bootdev_hunter); + result = 0; + + len = SIZE_MAX; + if (spec) { + trailing_strtoln_end(spec, NULL, &end); + len = end - spec; + } + + for (i = 0; i < n_ent; i++) { + struct bootdev_hunter *info = start + i; + const char *name = uclass_get_name(info->uclass); + int ret; + + log_debug("looking at %.*s for %s\n", + (int)max(strlen(name), len), spec, name); + if (spec && strncmp(spec, name, max(strlen(name), len))) { + if (info->uclass != UCLASS_ETH || + (strcmp("dhcp", spec) && strcmp("pxe", spec))) + continue; + } + ret = bootdev_hunt_drv(info, i, show); + if (ret) + result = ret; + } + + return result; +} + +int bootdev_unhunt(enum uclass_id id) +{ + struct bootdev_hunter *start; + int n_ent, i; + + start = ll_entry_start(struct bootdev_hunter, bootdev_hunter); + n_ent = ll_entry_count(struct bootdev_hunter, bootdev_hunter); + for (i = 0; i < n_ent; i++) { + struct bootdev_hunter *info = start + i; + + if (info->uclass == id) { + struct bootstd_priv *std; + int ret; + + ret = bootstd_get_priv(&std); + if (ret) + return log_msg_ret("std", ret); + if (!(std->hunters_used & BIT(i))) + return -EALREADY; + std->hunters_used &= ~BIT(i); + return 0; + } + } + + return -ENOENT; +} + +int bootdev_hunt_prio(enum bootdev_prio_t prio, bool show) +{ + struct bootdev_hunter *start; + int n_ent, i; + int result; + + start = ll_entry_start(struct bootdev_hunter, bootdev_hunter); + n_ent = ll_entry_count(struct bootdev_hunter, bootdev_hunter); + result = 0; + + log_debug("Hunting for priority %d\n", prio); + for (i = 0; i < n_ent; i++) { + struct bootdev_hunter *info = start + i; + int ret; + + if (prio != info->prio) + continue; + ret = bootdev_hunt_drv(info, i, show); + log_debug("bootdev_hunt_drv() return %d\n", ret); + if (ret && ret != -ENOENT) + result = ret; + } + log_debug("exit %d\n", result); + + return result; +} + +void bootdev_list_hunters(struct bootstd_priv *std) +{ + struct bootdev_hunter *orig, *start; + int n_ent, i; + + orig = ll_entry_start(struct bootdev_hunter, bootdev_hunter); + n_ent = ll_entry_count(struct bootdev_hunter, bootdev_hunter); + + /* + * workaround for strange bug in clang-12 which sees all the below data + * as zeroes. Any access of start seems to fix it, such as + * + * printf("%p", start); + * + * Use memcpy() to force the correct behaviour. + */ + memcpy(&start, &orig, sizeof(orig)); + printf("%4s %4s %-15s %s\n", "Prio", "Used", "Uclass", "Hunter"); + printf("%4s %4s %-15s %s\n", "----", "----", "---------------", "---------------"); + for (i = 0; i < n_ent; i++) { + struct bootdev_hunter *info = start + i; + + printf("%4d %4s %-15s %s\n", info->prio, + std->hunters_used & BIT(i) ? "*" : "", + uclass_get_name(info->uclass), + info->drv ? info->drv->name : "(none)"); + } + + printf("(total hunters: %d)\n", n_ent); +} + +static int bootdev_post_bind(struct udevice *dev) +{ + struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev); + + INIT_LIST_HEAD(&ucp->bootflow_head); + + return 0; +} + +static int bootdev_pre_unbind(struct udevice *dev) +{ + bootdev_clear_bootflows(dev); + + return 0; +} + +UCLASS_DRIVER(bootdev) = { + .id = UCLASS_BOOTDEV, + .name = "bootdev", + .flags = DM_UC_FLAG_SEQ_ALIAS, + .per_device_plat_auto = sizeof(struct bootdev_uc_plat), + .post_bind = bootdev_post_bind, + .pre_unbind = bootdev_pre_unbind, +}; |