diff options
| author | Tom Rini <[email protected]> | 2022-04-25 16:02:27 -0400 |
|---|---|---|
| committer | Tom Rini <[email protected]> | 2022-04-25 16:02:27 -0400 |
| commit | 8cfac237b9814d52c843e939a05fc211ba3906de (patch) | |
| tree | 975bba394b3c71a225283c2cb04ecda5c4bb189d /boot | |
| parent | bc9da9fb50ac3ba7603487a0366d4db60b984812 (diff) | |
| parent | e7b2ce191ecab558b130b3b926dddcfc7231deb0 (diff) | |
Merge branch '2022-04-25-initial-implementation-of-stdboot'
To quote the author:
The bootflow feature provide a built-in way for U-Boot to automatically
boot an Operating System without custom scripting and other customisation.
This is called 'standard boot' since it provides a standard way for
U-Boot to boot a distro, without scripting.
It introduces the following concepts:
- bootdev - a device which can hold a distro
- bootmeth - a method to scan a bootdev to find bootflows (owned by
U-Boot)
- bootflow - a description of how to boot (owned by the distro)
This series provides an implementation of these, enabled to scan for
bootflows from MMC, USB and Ethernet. It supports the existing distro
boot as well as the EFI loader flow (bootefi/bootmgr). It works
similiarly to the existing script-based approach, but is native to
U-Boot.
With this we can boot on a Raspberry Pi 3 with just one command:
bootflow scan -lb
which means to scan, listing (-l) each bootflow and trying to boot each
one (-b). The final patch shows this.
With a standard way to identify boot devices, booting become easier. It
also should be possible to support U-Boot scripts, for backwards
compatibility only.
...
The design is described in these two documents:
https://drive.google.com/file/d/1ggW0KJpUOR__vBkj3l61L2dav4ZkNC12/view?usp=sharing
https://drive.google.com/file/d/1kTrflO9vvGlKp-ZH_jlgb9TY3WYG6FF9/view?usp=sharing
Diffstat (limited to 'boot')
| -rw-r--r-- | boot/Kconfig | 115 | ||||
| -rw-r--r-- | boot/Makefile | 15 | ||||
| -rw-r--r-- | boot/bootdev-uclass.c | 649 | ||||
| -rw-r--r-- | boot/bootflow.c | 411 | ||||
| -rw-r--r-- | boot/bootmeth-uclass.c | 333 | ||||
| -rw-r--r-- | boot/bootmeth_distro.c | 143 | ||||
| -rw-r--r-- | boot/bootmeth_efi.c | 188 | ||||
| -rw-r--r-- | boot/bootmeth_efi_mgr.c | 86 | ||||
| -rw-r--r-- | boot/bootmeth_pxe.c | 186 | ||||
| -rw-r--r-- | boot/bootmeth_sandbox.c | 69 | ||||
| -rw-r--r-- | boot/bootmeth_script.c | 139 | ||||
| -rw-r--r-- | boot/bootstd-uclass.c | 183 | ||||
| -rw-r--r-- | boot/system_bootdev.c | 66 |
13 files changed, 2582 insertions, 1 deletions
diff --git a/boot/Kconfig b/boot/Kconfig index ec5b956490d..4b0802b8c6e 100644 --- a/boot/Kconfig +++ b/boot/Kconfig @@ -292,6 +292,117 @@ endif # SPL endif # FIT +config BOOTSTD + bool "Standard boot support" + default y + depends on DM && OF_CONTROL && BLK + help + U-Boot supports a standard way of locating something to boot, + typically an Operating System such as Linux, provided by a distro such + as Arch Linux or Debian. Enable this to support iterating through + available bootdevs and using bootmeths to find bootflows suitable for + booting. + + Standard boot is not a standard way of booting, just a framework + within U-Boot for supporting all the different ways that exist. + + Terminology: + + - bootdev - a device which can hold a distro (e.g. MMC) + - bootmeth - a method to scan a bootdev to find bootflows (owned by + U-Boot) + - bootflow - a description of how to boot (owned by the distro) + +config BOOTSTD_FULL + bool "Enhanced features for standard boot" + default y if SANDBOX + help + This enables various useful features for standard boot, which are not + essential for operation: + + - bootdev, bootmeth commands + - extra features in the bootflow command + - support for selecting the ordering of bootmeths ("bootmeth order") + - support for selecting the ordering of bootdevs using the devicetree + as well as the "boot_targets" environment variable + +if BOOTSTD + +config BOOTSTD_BOOTCOMMAND + bool "Use bootstd to boot" + default y if !DISTRO_DEFAULTS + help + Enable this to select a default boot-command suitable for booting + with standard boot. This can be overridden by the board if needed, + but the default command should be enough for most boards which use + standard boot. + + For now this is only selected if distro boot is NOT used, since + standard boot does not support all of the features of distro boot + yet. + +config BOOTMETH_DISTRO + bool "Bootdev support for distro boot" + depends on CMD_PXE + default y + help + Enables support for distro boot using bootdevs. This makes the + bootdevs look for a 'extlinux/extlinux.conf' on each filesystem + they scan. + + This provides a way to try out standard boot on an existing boot flow. + +config BOOTMETH_DISTRO_PXE + bool "Bootdev support for distro boot over network" + depends on CMD_PXE && CMD_NET && DM_ETH + default y + help + Enables support for distro boot using bootdevs. This makes the + bootdevs look for a 'extlinux/extlinux.conf' on the tftp server. + + This provides a way to try out standard boot on an existing boot flow. + +config BOOTMETH_EFILOADER + bool "Bootdev support for EFI boot" + depends on CMD_BOOTEFI + default y + help + Enables support for EFI boot using bootdevs. This makes the + bootdevs look for a 'boot<arch>.efi' on each filesystem + they scan. The resulting file is booted after enabling U-Boot's + EFI loader support. + + The <arch> depends on the architecture of the board: + + aa64 - aarch64 (ARM 64-bit) + arm - ARM 32-bit + ia32 - x86 32-bit + x64 - x86 64-bit + riscv32 - RISC-V 32-bit + riscv64 - RISC-V 64-bit + + This provides a way to try out standard boot on an existing boot flow. + +config BOOTMETH_SANDBOX + def_bool y + depends on SANDBOX + help + This is a sandbox bootmeth driver used for testing. It always returns + -ENOTSUPP when attempting to boot. + +config BOOTMETH_SCRIPT + bool "Bootdev support for U-Boot scripts" + default y if BOOTSTD_FULL + help + Enables support for booting a distro via a U-Boot script. This makes + the bootdevs look for a 'boot/boot.scr' file which can be used to + boot the distro. + + This provides a way to try out standard boot on an existing boot flow. + It is not enabled by default to save space. + +endif + config LEGACY_IMAGE_FORMAT bool "Enable support for the legacy image format" default y if !FIT_SIGNATURE @@ -1162,7 +1273,9 @@ config USE_BOOTCOMMAND config BOOTCOMMAND string "bootcmd value" depends on USE_BOOTCOMMAND && !USE_DEFAULT_ENV_FILE - default "run distro_bootcmd" if DISTRO_DEFAULTS + default "bootflow scan -lb" if BOOTSTD_BOOTCOMMAND && CMD_BOOTFLOW_FULL + default "bootflow scan" if BOOTSTD_BOOTCOMMAND && !CMD_BOOTFLOW_FULL + default "run distro_bootcmd" if !BOOTSTD_BOOTCOMMAND && DISTRO_DEFAULTS help This is the string of commands that will be used as bootcmd and if AUTOBOOT is set, automatically run. diff --git a/boot/Makefile b/boot/Makefile index 1b99e6ee33f..a70674259c1 100644 --- a/boot/Makefile +++ b/boot/Makefile @@ -18,6 +18,21 @@ endif obj-y += image.o image-board.o obj-$(CONFIG_ANDROID_AB) += android_ab.o obj-$(CONFIG_ANDROID_BOOT_IMAGE) += image-android.o image-android-dt.o + +obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootdev-uclass.o system_bootdev.o +obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootflow.o +obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootmeth-uclass.o +obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootstd-uclass.o + +obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_DISTRO) += bootmeth_distro.o +obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_DISTRO_PXE) += bootmeth_pxe.o +obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_EFILOADER) += bootmeth_efi.o +obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_SANDBOX) += bootmeth_sandbox.o +obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_SCRIPT) += bootmeth_script.o +ifdef CONFIG_$(SPL_TPL_)BOOTSTD_FULL +obj-$(CONFIG_$(SPL_TPL_)CMD_BOOTEFI_BOOTMGR) += bootmeth_efi_mgr.o +endif + obj-$(CONFIG_$(SPL_TPL_)OF_LIBFDT) += image-fdt.o obj-$(CONFIG_$(SPL_TPL_)FIT_SIGNATURE) += fdt_region.o obj-$(CONFIG_$(SPL_TPL_)FIT) += image-fit.o diff --git a/boot/bootdev-uclass.c b/boot/bootdev-uclass.c new file mode 100644 index 00000000000..1ede933c2f2 --- /dev/null +++ b/boot/bootdev-uclass.c @@ -0,0 +1,649 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2021 Google LLC + * Written by Simon Glass <[email protected]> + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include <common.h> +#include <dm.h> +#include <bootdev.h> +#include <bootflow.h> +#include <bootmeth.h> +#include <bootstd.h> +#include <env.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 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, +}; diff --git a/boot/bootflow.c b/boot/bootflow.c new file mode 100644 index 00000000000..24ba3c34660 --- /dev/null +++ b/boot/bootflow.c @@ -0,0 +1,411 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2021 Google LLC + * Written by Simon Glass <[email protected]> + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include <common.h> +#include <bootdev.h> +#include <bootflow.h> +#include <bootmeth.h> +#include <bootstd.h> +#include <dm.h> +#include <malloc.h> +#include <dm/device-internal.h> +#include <dm/uclass-internal.h> + +/* error codes used to signal running out of things */ +enum { + BF_NO_MORE_PARTS = -ESHUTDOWN, + BF_NO_MORE_DEVICES = -ENODEV, +}; + +/** + * bootflow_state - name for each state + * + * See enum bootflow_state_t for what each of these means + */ +static const char *const bootflow_state[BOOTFLOWST_COUNT] = { + "base", + "media", + "part", + "fs", + "file", + "ready", +}; + +const char *bootflow_state_get_name(enum bootflow_state_t state) +{ + /* This doesn't need to be a useful name, since it will never occur */ + if (state < 0 || state >= BOOTFLOWST_COUNT) + return "?"; + + return bootflow_state[state]; +} + +int bootflow_first_glob(struct bootflow **bflowp) +{ + struct bootstd_priv *std; + int ret; + + ret = bootstd_get_priv(&std); + if (ret) + return ret; + + if (list_empty(&std->glob_head)) + return -ENOENT; + + *bflowp = list_first_entry(&std->glob_head, struct bootflow, + glob_node); + + return 0; +} + +int bootflow_next_glob(struct bootflow **bflowp) +{ + struct bootstd_priv *std; + struct bootflow *bflow = *bflowp; + int ret; + + ret = bootstd_get_priv(&std); + if (ret) + return ret; + + *bflowp = NULL; + + if (list_is_last(&bflow->glob_node, &std->glob_head)) + return -ENOENT; + + *bflowp = list_entry(bflow->glob_node.next, struct bootflow, glob_node); + + return 0; +} + +void bootflow_iter_init(struct bootflow_iter *iter, int flags) +{ + memset(iter, '\0', sizeof(*iter)); + iter->flags = flags; +} + +void bootflow_iter_uninit(struct bootflow_iter *iter) +{ + free(iter->dev_order); + free(iter->method_order); +} + +int bootflow_iter_drop_bootmeth(struct bootflow_iter *iter, + const struct udevice *bmeth) +{ + /* We only support disabling the current bootmeth */ + if (bmeth != iter->method || iter->cur_method >= iter->num_methods || + iter->method_order[iter->cur_method] != bmeth) + return -EINVAL; + + 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--; + + return 0; +} + +static void bootflow_iter_set_dev(struct bootflow_iter *iter, + struct udevice *dev) +{ + iter->dev = dev; + if ((iter->flags & (BOOTFLOWF_SHOW | BOOTFLOWF_SINGLE_DEV)) == + BOOTFLOWF_SHOW) { + if (dev) + printf("Scanning bootdev '%s':\n", dev->name); + else + printf("No more bootdevs\n"); + } +} + +/** + * iter_incr() - Move to the next item (method, part, bootdev) + * + * Return: 0 if OK, BF_NO_MORE_DEVICES if there are no more bootdevs + */ +static int iter_incr(struct bootflow_iter *iter) +{ + struct udevice *dev; + int ret; + + 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) { + iter->method = iter->method_order[iter->cur_method]; + return 0; + } + } + + /* No more bootmeths; start at the first one, and... */ + iter->cur_method = 0; + iter->method = iter->method_order[iter->cur_method]; + + if (iter->err != BF_NO_MORE_PARTS) { + /* ...select next partition */ + if (++iter->part <= iter->max_part) + return 0; + } + + /* No more partitions; start at the first one and...*/ + iter->part = 0; + + /* + * Note: as far as we know, there is no partition table on the next + * bootdev, so set max_part to 0 until we discover otherwise. See + * bootdev_find_in_blk() for where this is set. + */ + iter->max_part = 0; + + /* ...select next bootdev */ + if (iter->flags & BOOTFLOWF_SINGLE_DEV) { + ret = -ENOENT; + } else if (++iter->cur_dev == iter->num_devs) { + ret = -ENOENT; + bootflow_iter_set_dev(iter, NULL); + } else { + dev = iter->dev_order[iter->cur_dev]; + ret = device_probe(dev); + if (!log_msg_ret("probe", ret)) + bootflow_iter_set_dev(iter, dev); + } + + /* if there are no more bootdevs, give up */ + if (ret) + return log_msg_ret("incr", BF_NO_MORE_DEVICES); + + return 0; +} + +/** + * bootflow_check() - Check if a bootflow can be obtained + * + * @iter: Provides part, bootmeth to use + * @bflow: Bootflow to update on success + * Return: 0 if OK, -ENOSYS if there is no bootflow support on this device, + * BF_NO_MORE_PARTS if there are no more partitions on bootdev + */ +static int bootflow_check(struct bootflow_iter *iter, struct bootflow *bflow) +{ + struct udevice *dev; + int ret; + + dev = iter->dev; + ret = bootdev_get_bootflow(dev, iter, bflow); + + /* If we got a valid bootflow, return it */ + if (!ret) { + log_debug("Bootdevice '%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", + dev->name, iter->part, iter->method->name, ret); + /* + * For 'all' we return all bootflows, even + * those with errors + */ + if (iter->flags & BOOTFLOWF_ALL) + return log_msg_ret("all", ret); + } + if (ret) + return log_msg_ret("check", ret); + + return 0; +} + +int bootflow_scan_bootdev(struct udevice *dev, struct bootflow_iter *iter, + int flags, struct bootflow *bflow) +{ + int ret; + + bootflow_iter_init(iter, flags); + + ret = bootdev_setup_iter_order(iter, &dev); + if (ret) + return log_msg_ret("obdev", -ENODEV); + bootflow_iter_set_dev(iter, dev); + + ret = bootmeth_setup_iter_order(iter); + if (ret) + return log_msg_ret("obmeth", -ENODEV); + + /* Find the first bootmeth (there must be at least one!) */ + iter->method = iter->method_order[iter->cur_method]; + + ret = bootflow_check(iter, bflow); + if (ret) { + if (ret != BF_NO_MORE_PARTS && ret != -ENOSYS) { + if (iter->flags & BOOTFLOWF_ALL) + return log_msg_ret("all", ret); + } + iter->err = ret; + ret = bootflow_scan_next(iter, bflow); + if (ret) + return log_msg_ret("get", ret); + } + + return 0; +} + +int bootflow_scan_first(struct bootflow_iter *iter, int flags, + struct bootflow *bflow) +{ + int ret; + + ret = bootflow_scan_bootdev(NULL, iter, flags, bflow); + if (ret) + return log_msg_ret("start", ret); + + return 0; +} + +int bootflow_scan_next(struct bootflow_iter *iter, struct bootflow *bflow) +{ + int ret; + + do { + ret = iter_incr(iter); + if (ret == BF_NO_MORE_DEVICES) + return log_msg_ret("done", ret); + + if (!ret) { + ret = bootflow_check(iter, bflow); + if (!ret) + return 0; + iter->err = ret; + if (ret != BF_NO_MORE_PARTS && ret != -ENOSYS) { + if (iter->flags & BOOTFLOWF_ALL) + return log_msg_ret("all", ret); + } + } else { + iter->err = ret; + } + + } while (1); +} + +void bootflow_free(struct bootflow *bflow) +{ + free(bflow->name); + free(bflow->subdir); + free(bflow->fname); + free(bflow->buf); +} + +void bootflow_remove(struct bootflow *bflow) +{ + list_del(&bflow->bm_node); + list_del(&bflow->glob_node); + + bootflow_free(bflow); + free(bflow); +} + +int bootflow_boot(struct bootflow *bflow) +{ + int ret; + + if (bflow->state != BOOTFLOWST_READY) + return log_msg_ret("load", -EPROTO); + + ret = bootmeth_boot(bflow->method, bflow); + if (ret) + return log_msg_ret("boot", ret); + + /* + * internal error, should not get here since we should have booted + * something or returned an error + */ + + return log_msg_ret("end", -EFAULT); +} + +int bootflow_run_boot(struct bootflow_iter *iter, struct bootflow *bflow) +{ + int ret; + + printf("** Booting bootflow '%s' with %s\n", bflow->name, + bflow->method->name); + ret = bootflow_boot(bflow); + if (!IS_ENABLED(CONFIG_BOOTSTD_FULL)) { + printf("Boot failed (err=%d)\n", ret); + return ret; + } + + switch (ret) { + case -EPROTO: + printf("Bootflow not loaded (state '%s')\n", + bootflow_state_get_name(bflow->state)); + break; + case -ENOSYS: + printf("Boot method '%s' not supported\n", bflow->method->name); + break; + case -ENOTSUPP: + /* Disable this bootflow for this iteration */ + if (iter) { + int ret2; + + ret2 = bootflow_iter_drop_bootmeth(iter, bflow->method); + if (!ret2) { + printf("Boot method '%s' failed and will not be retried\n", + bflow->method->name); + } + } + + break; + default: + printf("Boot failed (err=%d)\n", ret); + break; + } + + return ret; +} + +int bootflow_iter_uses_blk_dev(const struct bootflow_iter *iter) +{ + const struct udevice *media = dev_get_parent(iter->dev); + enum uclass_id id = device_get_uclass_id(media); + + log_debug("uclass %d: %s\n", id, uclass_get_name(id)); + if (id != UCLASS_ETH && id != UCLASS_BOOTSTD) + return 0; + + return -ENOTSUPP; +} + +int bootflow_iter_uses_network(const struct bootflow_iter *iter) +{ + const struct udevice *media = dev_get_parent(iter->dev); + enum uclass_id id = device_get_uclass_id(media); + + log_debug("uclass %d: %s\n", id, uclass_get_name(id)); + if (id == UCLASS_ETH) + return 0; + + return -ENOTSUPP; +} + +int bootflow_iter_uses_system(const struct bootflow_iter *iter) +{ + const struct udevice *media = dev_get_parent(iter->dev); + enum uclass_id id = device_get_uclass_id(media); + + log_debug("uclass %d: %s\n", id, uclass_get_name(id)); + if (id == UCLASS_BOOTSTD) + return 0; + + return -ENOTSUPP; +} diff --git a/boot/bootmeth-uclass.c b/boot/bootmeth-uclass.c new file mode 100644 index 00000000000..c040d5f92b2 --- /dev/null +++ b/boot/bootmeth-uclass.c @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2021 Google LLC + * Written by Simon Glass <[email protected]> + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include <common.h> +#include <blk.h> +#include <bootflow.h> +#include <bootmeth.h> +#include <bootstd.h> +#include <dm.h> +#include <env_internal.h> +#include <fs.h> +#include <malloc.h> +#include <mapmem.h> +#include <dm/uclass-internal.h> + +DECLARE_GLOBAL_DATA_PTR; + +int bootmeth_check(struct udevice *dev, struct bootflow_iter *iter) +{ + const struct bootmeth_ops *ops = bootmeth_get_ops(dev); + + if (!ops->check) + return 0; + + return ops->check(dev, iter); +} + +int bootmeth_read_bootflow(struct udevice *dev, struct bootflow *bflow) +{ + const struct bootmeth_ops *ops = bootmeth_get_ops(dev); + + if (!ops->read_bootflow) + return -ENOSYS; + + return ops->read_bootflow(dev, bflow); +} + +int bootmeth_boot(struct udevice *dev, struct bootflow *bflow) +{ + const struct bootmeth_ops *ops = bootmeth_get_ops(dev); + + if (!ops->boot) + return -ENOSYS; + + return ops->boot(dev, bflow); +} + +int bootmeth_read_file(struct udevice *dev, struct bootflow *bflow, + const char *file_path, ulong addr, ulong *sizep) +{ + const struct bootmeth_ops *ops = bootmeth_get_ops(dev); + + if (!ops->read_file) + return -ENOSYS; + + return ops->read_file(dev, bflow, file_path, addr, sizep); +} + +/** + * bootmeth_setup_iter_order() - Set up the ordering of bootmeths to scan + * + * This sets up the ordering information in @iter, based on the selected + * ordering of the bootmethds in bootstd_priv->bootmeth_order. If there is no + * ordering there, then all bootmethods are added + * + * @iter: Iterator to update with the order + * Return: 0 if OK, -ENOENT if no bootdevs, -ENOMEM if out of memory, other -ve + * on other error + */ +int bootmeth_setup_iter_order(struct bootflow_iter *iter) +{ + struct bootstd_priv *std; + struct udevice **order; + int count; + int ret; + + ret = bootstd_get_priv(&std); + if (ret) + return ret; + + /* Create an array large enough */ + count = std->bootmeth_count ? std->bootmeth_count : + uclass_id_count(UCLASS_BOOTMETH); + if (!count) + return log_msg_ret("count", -ENOENT); + + order = calloc(count, sizeof(struct udevice *)); + if (!order) + return log_msg_ret("order", -ENOMEM); + + /* If we have an ordering, copy it */ + if (IS_ENABLED(CONFIG_BOOTSTD_FULL) && std->bootmeth_count) { + memcpy(order, std->bootmeth_order, + count * sizeof(struct bootmeth *)); + } else { + struct udevice *dev; + int i, upto; + + /* + * Get a list of bootmethods, 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_get_device_by_seq(UCLASS_BOOTMETH, i, + &dev); + if (!ret) + order[upto++] = dev; + } + count = upto; + } + + iter->method_order = order; + iter->num_methods = count; + iter->cur_method = 0; + + return 0; +} + +int bootmeth_set_order(const char *order_str) +{ + struct bootstd_priv *std; + struct udevice **order; + int count, ret, i, len; + const char *s, *p; + + ret = bootstd_get_priv(&std); + if (ret) + return ret; + + if (!order_str) { + free(std->bootmeth_order); + std->bootmeth_order = NULL; + std->bootmeth_count = 0; + return 0; + } + + /* Create an array large enough */ + count = uclass_id_count(UCLASS_BOOTMETH); + if (!count) + return log_msg_ret("count", -ENOENT); + + order = calloc(count + 1, sizeof(struct udevice *)); + if (!order) + return log_msg_ret("order", -ENOMEM); + + for (i = 0, s = order_str; *s && i < count; s = p + (*p == ' '), i++) { + struct udevice *dev; + + p = strchrnul(s, ' '); + len = p - s; + ret = uclass_find_device_by_namelen(UCLASS_BOOTMETH, s, len, + &dev); + if (ret) { + printf("Unknown bootmeth '%.*s'\n", len, s); + free(order); + return ret; + } + order[i] = dev; + } + order[i] = NULL; + free(std->bootmeth_order); + std->bootmeth_order = order; + std->bootmeth_count = i; + + return 0; +} + +/** + * setup_fs() - Set up read to read a file + * + * We must redo the setup before each filesystem operation. This function + * handles that, including setting the filesystem type if a block device is not + * being used + * + * @bflow: Information about file to try + * @desc: Block descriptor to read from (NULL if not a block device) + * Return: 0 if OK, -ve on error + */ +static int setup_fs(struct bootflow *bflow, struct blk_desc *desc) +{ + int ret; + + if (desc) { + ret = fs_set_blk_dev_with_part(desc, bflow->part); + if (ret) + return log_msg_ret("set", ret); + } else if (IS_ENABLED(CONFIG_BOOTSTD_FULL) && bflow->fs_type) { + fs_set_type(bflow->fs_type); + } + + return 0; +} + +int bootmeth_try_file(struct bootflow *bflow, struct blk_desc *desc, + const char *prefix, const char *fname) +{ + char path[200]; + loff_t size; + int ret, ret2; + + snprintf(path, sizeof(path), "%s%s", prefix ? prefix : "", fname); + log_debug("trying: %s\n", path); + + free(bflow->fname); + bflow->fname = strdup(path); + if (!bflow->fname) + return log_msg_ret("name", -ENOMEM); + + if (IS_ENABLED(CONFIG_BOOTSTD_FULL) && bflow->fs_type) + fs_set_type(bflow->fs_type); + + ret = fs_size(path, &size); + log_debug(" %s - err=%d\n", path, ret); + + /* Sadly FS closes the file after fs_size() so we must redo this */ + ret2 = setup_fs(bflow, desc); + if (ret2) + return log_msg_ret("fs", ret2); + + if (ret) + return log_msg_ret("size", ret); + + bflow->size = size; + bflow->state = BOOTFLOWST_FILE; + + return 0; +} + +int bootmeth_alloc_file(struct bootflow *bflow, uint size_limit, uint align) +{ + loff_t bytes_read; + ulong addr; + char *buf; + uint size; + int ret; + + size = bflow->size; + log_debug(" - script file size %x\n", size); + if (size > size_limit) + return log_msg_ret("chk", -E2BIG); + + buf = memalign(align, size + 1); + if (!buf) + return log_msg_ret("buf", -ENOMEM); + addr = map_to_sysmem(buf); + + ret = fs_read(bflow->fname, addr, 0, 0, &bytes_read); + if (ret) { + free(buf); + return log_msg_ret("read", ret); + } + if (size != bytes_read) + return log_msg_ret("bread", -EINVAL); + buf[size] = '\0'; + bflow->state = BOOTFLOWST_READY; + bflow->buf = buf; + + return 0; +} + +int bootmeth_common_read_file(struct udevice *dev, struct bootflow *bflow, + const char *file_path, ulong addr, ulong *sizep) +{ + struct blk_desc *desc = NULL; + loff_t len_read; + loff_t size; + int ret; + + if (bflow->blk) + desc = dev_get_uclass_plat(bflow->blk); + + ret = setup_fs(bflow, desc); + if (ret) + return log_msg_ret("fs", ret); + + ret = fs_size(file_path, &size); + if (ret) + return log_msg_ret("size", ret); + if (size > *sizep) + return log_msg_ret("spc", -ENOSPC); + + ret = setup_fs(bflow, desc); + if (ret) + return log_msg_ret("fs", ret); + + ret = fs_read(file_path, addr, 0, 0, &len_read); + if (ret) + return ret; + *sizep = len_read; + + return 0; +} + +#ifdef CONFIG_BOOTSTD_FULL +/** + * on_bootmeths() - Update the bootmeth order + * + * This will check for a valid baudrate and only apply it if valid. + */ +static int on_bootmeths(const char *name, const char *value, enum env_op op, + int flags) +{ + int ret; + + switch (op) { + case env_op_create: + case env_op_overwrite: + ret = bootmeth_set_order(value); + if (ret) + return 1; + return 0; + case env_op_delete: + bootmeth_set_order(NULL); + fallthrough; + default: + return 0; + } +} +U_BOOT_ENV_CALLBACK(bootmeths, on_bootmeths); +#endif /* CONFIG_BOOTSTD_FULL */ + +UCLASS_DRIVER(bootmeth) = { + .id = UCLASS_BOOTMETH, + .name = "bootmeth", + .flags = DM_UC_FLAG_SEQ_ALIAS, + .per_device_plat_auto = sizeof(struct bootmeth_uc_plat), +}; diff --git a/boot/bootmeth_distro.c b/boot/bootmeth_distro.c new file mode 100644 index 00000000000..2b41e654ade --- /dev/null +++ b/boot/bootmeth_distro.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Bootmethod for distro boot (syslinux boot from a block device) + * + * Copyright 2021 Google LLC + * Written by Simon Glass <[email protected]> + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include <common.h> +#include <bootdev.h> +#include <bootflow.h> +#include <bootmeth.h> +#include <bootstd.h> +#include <command.h> +#include <distro.h> +#include <dm.h> +#include <fs.h> +#include <malloc.h> +#include <mapmem.h> +#include <mmc.h> +#include <pxe_utils.h> + +static int disto_getfile(struct pxe_context *ctx, const char *file_path, + char *file_addr, ulong *sizep) +{ + struct distro_info *info = ctx->userdata; + ulong addr; + int ret; + + addr = simple_strtoul(file_addr, NULL, 16); + + /* Allow up to 1GB */ + *sizep = 1 << 30; + ret = bootmeth_read_file(info->dev, info->bflow, file_path, addr, + sizep); + if (ret) + return log_msg_ret("read", ret); + + return 0; +} + +static int distro_check(struct udevice *dev, struct bootflow_iter *iter) +{ + int ret; + + /* This only works on block devices */ + ret = bootflow_iter_uses_blk_dev(iter); + if (ret) + return log_msg_ret("blk", ret); + + return 0; +} + +static int distro_read_bootflow(struct udevice *dev, struct bootflow *bflow) +{ + struct blk_desc *desc; + const char *const *prefixes; + struct udevice *bootstd; + const char *prefix; + loff_t size; + int ret, i; + + ret = uclass_first_device_err(UCLASS_BOOTSTD, &bootstd); + if (ret) + return log_msg_ret("std", ret); + + /* If a block device, we require a partition table */ + if (bflow->blk && !bflow->part) + return -ENOENT; + + prefixes = bootstd_get_prefixes(bootstd); + i = 0; + desc = bflow->blk ? dev_get_uclass_plat(bflow->blk) : NULL; + do { + prefix = prefixes ? prefixes[i] : NULL; + + ret = bootmeth_try_file(bflow, desc, prefix, DISTRO_FNAME); + } while (ret && prefixes && prefixes[++i]); + if (ret) + return log_msg_ret("try", ret); + size = bflow->size; + + ret = bootmeth_alloc_file(bflow, 0x10000, 1); + if (ret) + return log_msg_ret("read", ret); + + return 0; +} + +static int distro_boot(struct udevice *dev, struct bootflow *bflow) +{ + struct cmd_tbl cmdtp = {}; /* dummy */ + struct pxe_context ctx; + struct distro_info info; + ulong addr; + int ret; + + addr = map_to_sysmem(bflow->buf); + info.dev = dev; + info.bflow = bflow; + ret = pxe_setup_ctx(&ctx, &cmdtp, disto_getfile, &info, true, + bflow->subdir); + if (ret) + return log_msg_ret("ctx", -EINVAL); + + ret = pxe_process(&ctx, addr, false); + if (ret) + return log_msg_ret("bread", -EINVAL); + + return 0; +} + +static int distro_bootmeth_bind(struct udevice *dev) +{ + struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev); + + plat->desc = IS_ENABLED(CONFIG_BOOTSTD_FULL) ? + "Syslinux boot from a block device" : "syslinux"; + + return 0; +} + +static struct bootmeth_ops distro_bootmeth_ops = { + .check = distro_check, + .read_bootflow = distro_read_bootflow, + .read_file = bootmeth_common_read_file, + .boot = distro_boot, +}; + +static const struct udevice_id distro_bootmeth_ids[] = { + { .compatible = "u-boot,distro-syslinux" }, + { } +}; + +U_BOOT_DRIVER(bootmeth_distro) = { + .name = "bootmeth_distro", + .id = UCLASS_BOOTMETH, + .of_match = distro_bootmeth_ids, + .ops = &distro_bootmeth_ops, + .bind = distro_bootmeth_bind, +}; diff --git a/boot/bootmeth_efi.c b/boot/bootmeth_efi.c new file mode 100644 index 00000000000..d5438eb67b9 --- /dev/null +++ b/boot/bootmeth_efi.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Bootmethod for distro boot via EFI + * + * Copyright 2021 Google LLC + * Written by Simon Glass <[email protected]> + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include <common.h> +#include <bootdev.h> +#include <bootflow.h> +#include <bootmeth.h> +#include <command.h> +#include <dm.h> +#include <efi_loader.h> +#include <fs.h> +#include <malloc.h> +#include <mapmem.h> +#include <mmc.h> +#include <pxe_utils.h> + +#define EFI_DIRNAME "efi/boot/" + +/** + * get_efi_leafname() - Get the leaf name for the EFI file we expect + * + * @str: Place to put leaf name for this architecture, e.g. "bootaa64.efi". + * Must have at least 16 bytes of space + * @max_len: Length of @str, must be >=16 + */ +static int get_efi_leafname(char *str, int max_len) +{ + const char *base; + + if (max_len < 16) + return log_msg_ret("spc", -ENOSPC); + if (IS_ENABLED(CONFIG_ARM64)) + base = "bootaa64"; + else if (IS_ENABLED(CONFIG_ARM)) + base = "bootarm"; + else if (IS_ENABLED(CONFIG_X86_RUN_32BIT)) + base = "bootia32"; + else if (IS_ENABLED(CONFIG_X86_RUN_64BIT)) + base = "bootx64"; + else if (IS_ENABLED(CONFIG_ARCH_RV32I)) + base = "bootriscv32"; + else if (IS_ENABLED(CONFIG_ARCH_RV64I)) + base = "bootriscv64"; + else if (IS_ENABLED(CONFIG_SANDBOX)) + base = "bootsbox"; + else + return -EINVAL; + + strcpy(str, base); + strcat(str, ".efi"); + + return 0; +} + +static int efiload_read_file(struct blk_desc *desc, struct bootflow *bflow) +{ + const struct udevice *media_dev; + int size = bflow->size; + const char *dev_name; + char devnum_str[9]; + char dirname[200]; + char *last_slash; + int ret; + + ret = bootmeth_alloc_file(bflow, 0x2000000, 0x10000); + if (ret) + return log_msg_ret("read", ret); + + /* + * This is a horrible hack to tell EFI about this boot device. Once we + * unify EFI with the rest of U-Boot we can clean this up. The same hack + * exists in multiple places, e.g. in the fs, tftp and load commands. + * + * Once we can clean up the EFI code to make proper use of driver model, + * this can go away. + */ + media_dev = dev_get_parent(bflow->dev); + snprintf(devnum_str, sizeof(devnum_str), "%x", dev_seq(media_dev)); + + strlcpy(dirname, bflow->fname, sizeof(dirname)); + last_slash = strrchr(dirname, '/'); + if (last_slash) + *last_slash = '\0'; + + log_debug("setting bootdev %s, %s, %s, %p, %x\n", + dev_get_uclass_name(media_dev), devnum_str, bflow->fname, + bflow->buf, size); + dev_name = device_get_uclass_id(media_dev) == UCLASS_MASS_STORAGE ? + "usb" : dev_get_uclass_name(media_dev); + efi_set_bootdev(dev_name, devnum_str, bflow->fname, bflow->buf, size); + + return 0; +} + +static int distro_efi_check(struct udevice *dev, struct bootflow_iter *iter) +{ + int ret; + + /* This only works on block devices */ + ret = bootflow_iter_uses_blk_dev(iter); + if (ret) + return log_msg_ret("blk", ret); + + return 0; +} + +static int distro_efi_read_bootflow(struct udevice *dev, struct bootflow *bflow) +{ + struct blk_desc *desc = NULL; + char fname[sizeof(EFI_DIRNAME) + 16]; + int ret; + + /* We require a partition table */ + if (!bflow->part) + return -ENOENT; + + strcpy(fname, EFI_DIRNAME); + ret = get_efi_leafname(fname + strlen(fname), + sizeof(fname) - strlen(fname)); + if (ret) + return log_msg_ret("leaf", ret); + + if (bflow->blk) + desc = dev_get_uclass_plat(bflow->blk); + ret = bootmeth_try_file(bflow, desc, NULL, fname); + if (ret) + return log_msg_ret("try", ret); + + ret = efiload_read_file(desc, bflow); + if (ret) + return log_msg_ret("read", -EINVAL); + + return 0; +} + +int distro_efi_boot(struct udevice *dev, struct bootflow *bflow) +{ + char cmd[50]; + + /* + * At some point we can add a real interface to bootefi so we can call + * this directly. For now, go through the CLI like distro boot. + */ + snprintf(cmd, sizeof(cmd), "bootefi %lx %lx", + (ulong)map_to_sysmem(bflow->buf), + (ulong)map_to_sysmem(gd->fdt_blob)); + if (run_command(cmd, 0)) + return log_msg_ret("run", -EINVAL); + + return 0; +} + +static int distro_bootmeth_efi_bind(struct udevice *dev) +{ + struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev); + + plat->desc = IS_ENABLED(CONFIG_BOOTSTD_FULL) ? + "EFI boot from an .efi file" : "EFI"; + + return 0; +} + +static struct bootmeth_ops distro_efi_bootmeth_ops = { + .check = distro_efi_check, + .read_bootflow = distro_efi_read_bootflow, + .read_file = bootmeth_common_read_file, + .boot = distro_efi_boot, +}; + +static const struct udevice_id distro_efi_bootmeth_ids[] = { + { .compatible = "u-boot,distro-efi" }, + { } +}; + +U_BOOT_DRIVER(bootmeth_efi) = { + .name = "bootmeth_efi", + .id = UCLASS_BOOTMETH, + .of_match = distro_efi_bootmeth_ids, + .ops = &distro_efi_bootmeth_ops, + .bind = distro_bootmeth_efi_bind, +}; diff --git a/boot/bootmeth_efi_mgr.c b/boot/bootmeth_efi_mgr.c new file mode 100644 index 00000000000..a6914466db7 --- /dev/null +++ b/boot/bootmeth_efi_mgr.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Bootmethod for EFI boot manager + * + * Copyright 2021 Google LLC + * Written by Simon Glass <[email protected]> + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include <common.h> +#include <bootdev.h> +#include <bootflow.h> +#include <bootmeth.h> +#include <command.h> +#include <dm.h> + +static int efi_mgr_check(struct udevice *dev, struct bootflow_iter *iter) +{ + int ret; + + /* Must be an bootstd device */ + ret = bootflow_iter_uses_system(iter); + if (ret) + return log_msg_ret("net", ret); + + return 0; +} + +static int efi_mgr_read_bootflow(struct udevice *dev, struct bootflow *bflow) +{ + /* + * Just assume there is something to boot since we don't have any way + * of knowing in advance + */ + bflow->state = BOOTFLOWST_READY; + + return 0; +} + +static int efi_mgr_read_file(struct udevice *dev, struct bootflow *bflow, + const char *file_path, ulong addr, ulong *sizep) +{ + /* Files are loaded by the 'bootefi bootmgr' command */ + + return -ENOSYS; +} + +static int efi_mgr_boot(struct udevice *dev, struct bootflow *bflow) +{ + int ret; + + /* Booting is handled by the 'bootefi bootmgr' command */ + ret = run_command("bootefi bootmgr", 0); + + return 0; +} + +static int bootmeth_efi_mgr_bind(struct udevice *dev) +{ + struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev); + + plat->desc = "EFI bootmgr flow"; + + return 0; +} + +static struct bootmeth_ops efi_mgr_bootmeth_ops = { + .check = efi_mgr_check, + .read_bootflow = efi_mgr_read_bootflow, + .read_file = efi_mgr_read_file, + .boot = efi_mgr_boot, +}; + +static const struct udevice_id efi_mgr_bootmeth_ids[] = { + { .compatible = "u-boot,efi-bootmgr" }, + { } +}; + +U_BOOT_DRIVER(bootmeth_zefi_mgr) = { + .name = "bootmeth_efi_mgr", + .id = UCLASS_BOOTMETH, + .of_match = efi_mgr_bootmeth_ids, + .ops = &efi_mgr_bootmeth_ops, + .bind = bootmeth_efi_mgr_bind, +}; diff --git a/boot/bootmeth_pxe.c b/boot/bootmeth_pxe.c new file mode 100644 index 00000000000..f1e2b4c7762 --- /dev/null +++ b/boot/bootmeth_pxe.c @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Bootmethod for distro boot using PXE (network boot) + * + * Copyright 2021 Google LLC + * Written by Simon Glass <[email protected]> + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include <common.h> +#include <bootdev.h> +#include <bootflow.h> +#include <bootmeth.h> +#include <command.h> +#include <distro.h> +#include <dm.h> +#include <fs.h> +#include <log.h> +#include <malloc.h> +#include <mapmem.h> +#include <mmc.h> +#include <net.h> +#include <pxe_utils.h> + +static int disto_pxe_getfile(struct pxe_context *ctx, const char *file_path, + char *file_addr, ulong *sizep) +{ + struct distro_info *info = ctx->userdata; + ulong addr; + int ret; + + addr = simple_strtoul(file_addr, NULL, 16); + ret = bootmeth_read_file(info->dev, info->bflow, file_path, addr, + sizep); + if (ret) + return log_msg_ret("read", ret); + + return 0; +} + +static int distro_pxe_check(struct udevice *dev, struct bootflow_iter *iter) +{ + int ret; + + /* This only works on network devices */ + ret = bootflow_iter_uses_network(iter); + if (ret) + return log_msg_ret("net", ret); + + return 0; +} + +static int distro_pxe_read_bootflow(struct udevice *dev, struct bootflow *bflow) +{ + const char *addr_str; + char fname[200]; + char *bootdir; + ulong addr; + ulong size; + char *buf; + int ret; + + addr_str = env_get("pxefile_addr_r"); + if (!addr_str) + return log_msg_ret("pxeb", -EPERM); + addr = simple_strtoul(addr_str, NULL, 16); + + log_debug("calling pxe_get()\n"); + ret = pxe_get(addr, &bootdir, &size); + log_debug("pxe_get() returned %d\n", ret); + if (ret) + return log_msg_ret("pxeb", ret); + bflow->size = size; + + /* Use the directory of the dhcp bootdir as our subdir, if provided */ + if (bootdir) { + const char *last_slash; + int path_len; + + last_slash = strrchr(bootdir, '/'); + if (last_slash) { + path_len = (last_slash - bootdir) + 1; + bflow->subdir = malloc(path_len + 1); + memcpy(bflow->subdir, bootdir, path_len); + bflow->subdir[path_len] = '\0'; + } + } + snprintf(fname, sizeof(fname), "%s%s", + bflow->subdir ? bflow->subdir : "", DISTRO_FNAME); + + bflow->fname = strdup(fname); + if (!bflow->fname) + return log_msg_ret("name", -ENOMEM); + + bflow->state = BOOTFLOWST_READY; + + /* Allocate the buffer, including the \0 byte added by get_pxe_file() */ + buf = malloc(size + 1); + if (!buf) + return log_msg_ret("buf", -ENOMEM); + memcpy(buf, map_sysmem(addr, 0), size + 1); + bflow->buf = buf; + + return 0; +} + +static int distro_pxe_read_file(struct udevice *dev, struct bootflow *bflow, + const char *file_path, ulong addr, ulong *sizep) +{ + char *tftp_argv[] = {"tftp", NULL, NULL, NULL}; + struct pxe_context *ctx = dev_get_priv(dev); + char file_addr[17]; + ulong size; + int ret; + + sprintf(file_addr, "%lx", addr); + tftp_argv[1] = file_addr; + tftp_argv[2] = (void *)file_path; + + if (do_tftpb(ctx->cmdtp, 0, 3, tftp_argv)) + return -ENOENT; + ret = pxe_get_file_size(&size); + if (ret) + return log_msg_ret("tftp", ret); + if (size > *sizep) + return log_msg_ret("spc", -ENOSPC); + *sizep = size; + + return 0; +} + +static int distro_pxe_boot(struct udevice *dev, struct bootflow *bflow) +{ + struct pxe_context *ctx = dev_get_priv(dev); + struct cmd_tbl cmdtp = {}; /* dummy */ + struct distro_info info; + ulong addr; + int ret; + + addr = map_to_sysmem(bflow->buf); + info.dev = dev; + info.bflow = bflow; + info.cmdtp = &cmdtp; + ret = pxe_setup_ctx(ctx, &cmdtp, disto_pxe_getfile, &info, false, + bflow->subdir); + if (ret) + return log_msg_ret("ctx", -EINVAL); + + ret = pxe_process(ctx, addr, false); + if (ret) + return log_msg_ret("bread", -EINVAL); + + return 0; +} + +static int distro_bootmeth_pxe_bind(struct udevice *dev) +{ + struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev); + + plat->desc = IS_ENABLED(CONFIG_BOOTSTD_FULL) ? + "PXE boot from a network device" : "PXE"; + + return 0; +} + +static struct bootmeth_ops distro_bootmeth_pxe_ops = { + .check = distro_pxe_check, + .read_bootflow = distro_pxe_read_bootflow, + .read_file = distro_pxe_read_file, + .boot = distro_pxe_boot, +}; + +static const struct udevice_id distro_bootmeth_pxe_ids[] = { + { .compatible = "u-boot,distro-pxe" }, + { } +}; + +U_BOOT_DRIVER(bootmeth_pxe) = { + .name = "bootmeth_pxe", + .id = UCLASS_BOOTMETH, + .of_match = distro_bootmeth_pxe_ids, + .ops = &distro_bootmeth_pxe_ops, + .bind = distro_bootmeth_pxe_bind, + .priv_auto = sizeof(struct pxe_context), +}; diff --git a/boot/bootmeth_sandbox.c b/boot/bootmeth_sandbox.c new file mode 100644 index 00000000000..13ec5e95e64 --- /dev/null +++ b/boot/bootmeth_sandbox.c @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Bootmethod for sandbox testing + * + * Copyright 2021 Google LLC + * Written by Simon Glass <[email protected]> + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include <common.h> +#include <bootdev.h> +#include <bootflow.h> +#include <bootmeth.h> +#include <dm.h> + +static int sandbox_check(struct udevice *dev, struct bootflow_iter *iter) +{ + return 0; +} + +static int sandbox_read_bootflow(struct udevice *dev, struct bootflow *bflow) +{ + /* pretend we are ready */ + bflow->state = BOOTFLOWST_READY; + + return 0; +} + +static int sandbox_read_file(struct udevice *dev, struct bootflow *bflow, + const char *file_path, ulong addr, ulong *sizep) +{ + return -ENOSYS; +} + +static int sandbox_boot(struct udevice *dev, struct bootflow *bflow) +{ + /* always fail: see bootflow_iter_disable() */ + return -ENOTSUPP; +} + +static int sandbox_bootmeth_bind(struct udevice *dev) +{ + struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev); + + plat->desc = "Sandbox boot for testing"; + + return 0; +} + +static struct bootmeth_ops sandbox_bootmeth_ops = { + .check = sandbox_check, + .read_bootflow = sandbox_read_bootflow, + .read_file = sandbox_read_file, + .boot = sandbox_boot, +}; + +static const struct udevice_id sandbox_bootmeth_ids[] = { + { .compatible = "u-boot,sandbox-syslinux" }, + { } +}; + +U_BOOT_DRIVER(bootmeth_sandbox) = { + .name = "bootmeth_sandbox", + .id = UCLASS_BOOTMETH, + .of_match = sandbox_bootmeth_ids, + .ops = &sandbox_bootmeth_ops, + .bind = sandbox_bootmeth_bind, +}; diff --git a/boot/bootmeth_script.c b/boot/bootmeth_script.c new file mode 100644 index 00000000000..d1c3f940037 --- /dev/null +++ b/boot/bootmeth_script.c @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Bootmethod for booting via a U-Boot script + * + * Copyright 2021 Google LLC + * Written by Simon Glass <[email protected]> + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include <common.h> +#include <blk.h> +#include <bootflow.h> +#include <bootmeth.h> +#include <bootstd.h> +#include <dm.h> +#include <env.h> +#include <fs.h> +#include <image.h> +#include <malloc.h> +#include <mapmem.h> + +#define SCRIPT_FNAME1 "boot.scr.uimg" +#define SCRIPT_FNAME2 "boot.scr" + +static int script_check(struct udevice *dev, struct bootflow_iter *iter) +{ + int ret; + + /* This only works on block devices */ + ret = bootflow_iter_uses_blk_dev(iter); + if (ret) + return log_msg_ret("blk", ret); + + return 0; +} + +static int script_read_bootflow(struct udevice *dev, struct bootflow *bflow) +{ + struct blk_desc *desc = NULL; + const char *const *prefixes; + struct udevice *bootstd; + const char *prefix; + int ret, i; + + ret = uclass_first_device_err(UCLASS_BOOTSTD, &bootstd); + if (ret) + return log_msg_ret("std", ret); + + /* We require a partition table */ + if (!bflow->part) + return -ENOENT; + + if (bflow->blk) + desc = dev_get_uclass_plat(bflow->blk); + + prefixes = bootstd_get_prefixes(bootstd); + i = 0; + do { + prefix = prefixes ? prefixes[i] : NULL; + + ret = bootmeth_try_file(bflow, desc, prefix, SCRIPT_FNAME1); + if (ret) + ret = bootmeth_try_file(bflow, desc, prefix, + SCRIPT_FNAME2); + } while (ret && prefixes && prefixes[++i]); + if (ret) + return log_msg_ret("try", ret); + + bflow->subdir = strdup(prefix ? prefix : ""); + if (!bflow->subdir) + return log_msg_ret("prefix", -ENOMEM); + + ret = bootmeth_alloc_file(bflow, 0x10000, 1); + if (ret) + return log_msg_ret("read", ret); + + return 0; +} + +static int script_boot(struct udevice *dev, struct bootflow *bflow) +{ + struct blk_desc *desc = dev_get_uclass_plat(bflow->blk); + ulong addr; + int ret; + + ret = env_set("devtype", blk_get_devtype(bflow->blk)); + if (!ret) + ret = env_set_hex("devnum", desc->devnum); + if (!ret) + ret = env_set("prefix", bflow->subdir); + if (!ret && IS_ENABLED(CONFIG_ARCH_SUNXI) && + !strcmp("mmc", blk_get_devtype(bflow->blk))) + ret = env_set_hex("mmc_bootdev", desc->devnum); + if (ret) + return log_msg_ret("env", ret); + + log_debug("devtype: %s\n", env_get("devtype")); + log_debug("devnum: %s\n", env_get("devnum")); + log_debug("prefix: %s\n", env_get("prefix")); + log_debug("mmc_bootdev: %s\n", env_get("mmc_bootdev")); + + addr = map_to_sysmem(bflow->buf); + ret = image_source_script(addr, NULL); + if (ret) + return log_msg_ret("boot", ret); + + return 0; +} + +static int script_bootmeth_bind(struct udevice *dev) +{ + struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev); + + plat->desc = IS_ENABLED(CONFIG_BOOTSTD_FULL) ? + "Script boot from a block device" : "script"; + + return 0; +} + +static struct bootmeth_ops script_bootmeth_ops = { + .check = script_check, + .read_bootflow = script_read_bootflow, + .read_file = bootmeth_common_read_file, + .boot = script_boot, +}; + +static const struct udevice_id script_bootmeth_ids[] = { + { .compatible = "u-boot,script" }, + { } +}; + +U_BOOT_DRIVER(bootmeth_script) = { + .name = "bootmeth_script", + .id = UCLASS_BOOTMETH, + .of_match = script_bootmeth_ids, + .ops = &script_bootmeth_ops, + .bind = script_bootmeth_bind, +}; diff --git a/boot/bootstd-uclass.c b/boot/bootstd-uclass.c new file mode 100644 index 00000000000..3c6c32ae604 --- /dev/null +++ b/boot/bootstd-uclass.c @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Uclass implementation for standard boot + * + * Copyright 2021 Google LLC + * Written by Simon Glass <[email protected]> + */ + +#include <common.h> +#include <bootflow.h> +#include <bootstd.h> +#include <dm.h> +#include <log.h> +#include <malloc.h> +#include <dm/device-internal.h> +#include <dm/lists.h> +#include <dm/read.h> +#include <dm/uclass-internal.h> + +DECLARE_GLOBAL_DATA_PTR; + +/* These are used if filename-prefixes is not present */ +const char *const default_prefixes[] = {"/", "/boot/", NULL}; + +static int bootstd_of_to_plat(struct udevice *dev) +{ + struct bootstd_priv *priv = dev_get_priv(dev); + int ret; + + if (IS_ENABLED(CONFIG_BOOTSTD_FULL)) { + /* Don't check errors since livetree and flattree are different */ + ret = dev_read_string_list(dev, "filename-prefixes", + &priv->prefixes); + dev_read_string_list(dev, "bootdev-order", + &priv->bootdev_order); + } + + return 0; +} + +static void bootstd_clear_glob_(struct bootstd_priv *priv) +{ + while (!list_empty(&priv->glob_head)) { + struct bootflow *bflow; + + bflow = list_first_entry(&priv->glob_head, struct bootflow, + glob_node); + bootflow_remove(bflow); + } +} + +void bootstd_clear_glob(void) +{ + struct bootstd_priv *std; + + if (bootstd_get_priv(&std)) + return; + + bootstd_clear_glob_(std); +} + +static int bootstd_remove(struct udevice *dev) +{ + struct bootstd_priv *priv = dev_get_priv(dev); + + free(priv->prefixes); + free(priv->bootdev_order); + bootstd_clear_glob_(priv); + + return 0; +} + +const char *const *const bootstd_get_bootdev_order(struct udevice *dev) +{ + struct bootstd_priv *std = dev_get_priv(dev); + + return std->bootdev_order; +} + +const char *const *const bootstd_get_prefixes(struct udevice *dev) +{ + struct bootstd_priv *std = dev_get_priv(dev); + + return std->prefixes ? std->prefixes : default_prefixes; +} + +int bootstd_get_priv(struct bootstd_priv **stdp) +{ + struct udevice *dev; + int ret; + + ret = uclass_first_device_err(UCLASS_BOOTSTD, &dev); + if (ret) + return ret; + *stdp = dev_get_priv(dev); + + return 0; +} + +static int bootstd_probe(struct udevice *dev) +{ + struct bootstd_priv *std = dev_get_priv(dev); + + INIT_LIST_HEAD(&std->glob_head); + + return 0; +} + +/* For now, bind the boormethod device if none are found in the devicetree */ +int dm_scan_other(bool pre_reloc_only) +{ + struct driver *drv = ll_entry_start(struct driver, driver); + const int n_ents = ll_entry_count(struct driver, driver); + struct udevice *dev, *bootstd; + int i, ret; + + /* These are not needed before relocation */ + if (!(gd->flags & GD_FLG_RELOC)) + return 0; + + /* Create a bootstd device if needed */ + uclass_find_first_device(UCLASS_BOOTSTD, &bootstd); + if (!bootstd) { + ret = device_bind_driver(gd->dm_root, "bootstd_drv", "bootstd", + &bootstd); + if (ret) + return log_msg_ret("bootstd", ret); + } + + /* If there are no bootmeth devices, create them */ + uclass_find_first_device(UCLASS_BOOTMETH, &dev); + if (dev) + return 0; + + for (i = 0; i < n_ents; i++, drv++) { + /* + * Disable EFI Manager for now as no one uses it so it is + * confusing + */ + if (drv->id == UCLASS_BOOTMETH && + strcmp("efi_mgr_bootmeth", drv->name)) { + const char *name = drv->name; + + if (!strncmp("bootmeth_", name, 9)) + name += 9; + ret = device_bind(bootstd, drv, name, 0, ofnode_null(), + &dev); + if (ret) + return log_msg_ret("meth", ret); + } + } + + /* Create the system bootdev too */ + ret = device_bind_driver(bootstd, "system_bootdev", "system-bootdev", + &dev); + if (ret) + return log_msg_ret("sys", ret); + + return 0; +} + +static const struct udevice_id bootstd_ids[] = { + { .compatible = "u-boot,boot-std" }, + { } +}; + +U_BOOT_DRIVER(bootstd_drv) = { + .id = UCLASS_BOOTSTD, + .name = "bootstd_drv", + .of_to_plat = bootstd_of_to_plat, + .probe = bootstd_probe, + .remove = bootstd_remove, + .of_match = bootstd_ids, + .priv_auto = sizeof(struct bootstd_priv), +}; + +UCLASS_DRIVER(bootstd) = { + .id = UCLASS_BOOTSTD, + .name = "bootstd", +#if CONFIG_IS_ENABLED(OF_REAL) + .post_bind = dm_scan_fdt_dev, +#endif +}; diff --git a/boot/system_bootdev.c b/boot/system_bootdev.c new file mode 100644 index 00000000000..432d2034780 --- /dev/null +++ b/boot/system_bootdev.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Bootdevice for system, used for bootmeths not tied to any partition device + * + * Copyright 2021 Google LLC + * Written by Simon Glass <[email protected]> + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include <common.h> +#include <bootdev.h> +#include <bootflow.h> +#include <bootmeth.h> +#include <command.h> +#include <distro.h> +#include <dm.h> +#include <log.h> +#include <net.h> + +static int system_get_bootflow(struct udevice *dev, struct bootflow_iter *iter, + struct bootflow *bflow) +{ + int ret; + + /* Must be an bootstd device */ + ret = bootflow_iter_uses_system(iter); + if (ret) + return log_msg_ret("net", ret); + + ret = bootmeth_check(bflow->method, iter); + if (ret) + return log_msg_ret("check", ret); + + ret = bootmeth_read_bootflow(bflow->method, bflow); + if (ret) + return log_msg_ret("method", ret); + + return 0; +} + +static int system_bootdev_bind(struct udevice *dev) +{ + struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev); + + ucp->prio = BOOTDEVP_6_SYSTEM; + + return 0; +} + +struct bootdev_ops system_bootdev_ops = { + .get_bootflow = system_get_bootflow, +}; + +static const struct udevice_id system_bootdev_ids[] = { + { .compatible = "u-boot,bootdev-system" }, + { } +}; + +U_BOOT_DRIVER(system_bootdev) = { + .name = "system_bootdev", + .id = UCLASS_BOOTDEV, + .ops = &system_bootdev_ops, + .bind = system_bootdev_bind, + .of_match = system_bootdev_ids, +}; |
