// SPDX-License-Identifier: GPL-2.0+ /* * Copyright 2021 Google LLC * Written by Simon Glass */ #define LOG_CATEGORY UCLASS_BOOTSTD #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 bootdev_uc_plat *ucp = dev_get_uclass_plat(bflow->dev); struct bootstd_priv *std; struct bootflow *new; int ret; assert(bflow->dev); 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); 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 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); 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) 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->part) { ret = fs_set_blk_dev_with_part(desc, bflow->part); bflow->state = BOOTFLOWST_PART; /* Use an #ifdef due to info.sys_ind */ #ifdef CONFIG_DOS_PARTITION log_debug("%s: Found partition %x type %x fstype %d\n", blk->name, bflow->part, info.sys_ind, ret ? -1 : fs_get_type()); #endif if (ret) return log_msg_ret("fs", ret); bflow->state = BOOTFLOWST_FS; } 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_err(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_err(&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; } int bootdev_setup_sibling_blk(struct udevice *blk, const char *drv_name) { struct udevice *parent, *dev; char dev_name[50]; int ret; snprintf(dev_name, sizeof(dev_name), "%s.%s", 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; char *p; if (device_get_uclass_id(dev) != UCLASS_BOOTDEV) return -EINVAL; /* This should always work if bootdev_setup_sibling_blk() was used */ p = strstr(dev->name, ".bootdev"); if (!p) return log_msg_ret("str", -EINVAL); len = p - dev->name; ret = device_find_child_by_namelen(parent, dev->name, len, &blk); if (ret) return log_msg_ret("find", 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; if (device_get_uclass_id(blk) != UCLASS_BLK) return -EINVAL; /* This should always work if bootdev_setup_sibling_blk() was used */ snprintf(dev_name, sizeof(dev_name), "%s.%s", 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; } /** * bootdev_find_by_label() - Convert a label string to a bootdev device * * Looks up a label name to find the associated bootdev. For example, if the * label name is "mmc2", this will find a bootdev for an mmc device whose * sequence number is 2. * * @label: Label string to convert, e.g. "mmc2" * @devp: Returns bootdev device corresponding to that boot label * Return: 0 if OK, -EINVAL if the label name (e.g. "mmc") does not refer to a * uclass, -ENOENT if no bootdev for that media has the sequence number * (e.g. 2) */ int bootdev_find_by_label(const char *label, struct udevice **devp) { struct udevice *media; struct uclass *uc; enum uclass_id id; const char *end; int seq; seq = trailing_strtoln_end(label, NULL, &end); id = uclass_get_by_namelen(label, end - label); log_debug("find %s: seq=%d, id=%d/%s\n", label, seq, id, uclass_get_name(id)); if (id == UCLASS_INVALID) { log_warning("Unknown uclass '%s' in label\n", label); return -EINVAL; } if (id == UCLASS_USB) id = UCLASS_MASS_STORAGE; /* 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; return 0; } log_debug("- no device in %s\n", media->name); } log_warning("Unknown seq %d for label '%s'\n", seq, label); return -ENOENT; } int bootdev_find_by_any(const char *name, struct udevice **devp) { struct udevice *dev; int ret, 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); if (ret) { printf("Cannot find bootdev '%s' (err=%d)\n", name, ret); return ret; } ret = device_probe(dev); } if (ret) { printf("Cannot probe bootdev '%s' (err=%d)\n", name, ret); return ret; } } else { ret = uclass_get_device_by_seq(UCLASS_BOOTDEV, seq, &dev); } if (ret) { printf("Cannot find '%s' (err=%d)\n", name, ret); return ret; } *devp = dev; 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); if (!ops->get_bootflow) return -ENOSYS; memset(bflow, '\0', sizeof(*bflow)); bflow->dev = dev; bflow->method = iter->method; bflow->state = BOOTFLOWST_BASE; 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); } } /** * h_cmp_bootdev() - Compare two bootdevs to find out which should go first * * @v1: struct udevice * of first bootdev device * @v2: struct udevice * of second bootdev device * Return: sort order (<0 if dev1 < dev2, ==0 if equal, >0 if dev1 > dev2) */ static int h_cmp_bootdev(const void *v1, const void *v2) { const struct udevice *dev1 = *(struct udevice **)v1; const struct udevice *dev2 = *(struct udevice **)v2; const struct bootdev_uc_plat *ucp1 = dev_get_uclass_plat(dev1); const struct bootdev_uc_plat *ucp2 = dev_get_uclass_plat(dev2); int diff; /* Use priority first */ diff = ucp1->prio - ucp2->prio; if (diff) return diff; /* Fall back to seq for devices of the same priority */ diff = dev_seq(dev1) - dev_seq(dev2); return diff; } /** * build_order() - Build the ordered list of bootdevs to use * * This builds an ordered list of devices by one of three methods: * - using the boot_targets environment variable, if non-empty * - using the bootdev-order devicetree property, if present * - sorted by priority and sequence number * * @bootstd: BOOTSTD device to use * @order: Bootdevs listed in default order * @max_count: Number of entries in @order * Return: number of bootdevs found in the ordering, or -E2BIG if the * boot_targets string is too long, or -EXDEV if the ordering produced 0 results */ static int build_order(struct udevice *bootstd, struct udevice **order, int max_count) { const char *overflow_target = NULL; const char *const *labels; struct udevice *dev; const char *targets; int i, ret, count; targets = env_get("boot_targets"); labels = IS_ENABLED(CONFIG_BOOTSTD_FULL) ? bootstd_get_bootdev_order(bootstd) : NULL; if (targets) { char str[BOOT_TARGETS_MAX_LEN]; char *target; if (strlen(targets) >= BOOT_TARGETS_MAX_LEN) return log_msg_ret("len", -E2BIG); /* make a copy of the string, since strok() will change it */ strcpy(str, targets); for (i = 0, target = strtok(str, " "); target; target = strtok(NULL, " ")) { ret = bootdev_find_by_label(target, &dev); if (!ret) { if (i == max_count) { overflow_target = target; break; } order[i++] = dev; } } count = i; } else if (labels) { int upto; upto = 0; for (i = 0; labels[i]; i++) { ret = bootdev_find_by_label(labels[i], &dev); if (!ret) { if (upto == max_count) { overflow_target = labels[i]; break; } order[upto++] = dev; } } count = upto; } else { /* sort them into priority order */ count = max_count; qsort(order, count, sizeof(struct udevice *), h_cmp_bootdev); } if (overflow_target) { log_warning("Expected at most %d bootdevs, but overflowed with boot_target '%s'\n", max_count, overflow_target); } if (!count) return log_msg_ret("targ", -EXDEV); return count; } int bootdev_setup_iter_order(struct bootflow_iter *iter, struct udevice **devp) { struct udevice *bootstd, *dev = *devp, **order; int upto, i; int count; int ret; ret = uclass_first_device_err(UCLASS_BOOTSTD, &bootstd); if (ret) { log_err("Missing bootstd device\n"); return log_msg_ret("std", ret); } /* Handle scanning a single device */ if (dev) { iter->flags |= BOOTFLOWF_SINGLE_DEV; return 0; } count = uclass_id_count(UCLASS_BOOTDEV); if (!count) return log_msg_ret("count", -ENOENT); order = calloc(count, sizeof(struct udevice *)); if (!order) return log_msg_ret("order", -ENOMEM); /* * Get a list of bootdevs, in seq order (i.e. using aliases). There may * be gaps so try to count up high enough to find them all. */ for (i = 0, upto = 0; upto < count && i < 20 + count * 2; i++) { ret = uclass_find_device_by_seq(UCLASS_BOOTDEV, i, &dev); if (!ret) order[upto++] = dev; } log_debug("Found %d bootdevs\n", count); if (upto != count) log_debug("Expected %d bootdevs, found %d using aliases\n", count, upto); count = build_order(bootstd, order, upto); if (count < 0) { free(order); return log_msg_ret("build", count); } iter->dev_order = order; iter->num_devs = count; iter->cur_dev = 0; dev = *order; ret = device_probe(dev); if (ret) return log_msg_ret("probe", ret); *devp = dev; return 0; } 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, };