summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Rini <trini@konsulko.com>2022-04-25 23:02:27 +0300
committerTom Rini <trini@konsulko.com>2022-04-25 23:02:27 +0300
commit8cfac237b9814d52c843e939a05fc211ba3906de (patch)
tree975bba394b3c71a225283c2cb04ecda5c4bb189d
parentbc9da9fb50ac3ba7603487a0366d4db60b984812 (diff)
parente7b2ce191ecab558b130b3b926dddcfc7231deb0 (diff)
downloadu-boot-8cfac237b9814d52c843e939a05fc211ba3906de.tar.xz
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
-rw-r--r--MAINTAINERS21
-rw-r--r--arch/sandbox/dts/sandbox.dts4
-rw-r--r--arch/sandbox/dts/sandbox.dtsi5
-rw-r--r--arch/sandbox/dts/test.dts18
-rw-r--r--boot/Kconfig115
-rw-r--r--boot/Makefile15
-rw-r--r--boot/bootdev-uclass.c649
-rw-r--r--boot/bootflow.c411
-rw-r--r--boot/bootmeth-uclass.c333
-rw-r--r--boot/bootmeth_distro.c143
-rw-r--r--boot/bootmeth_efi.c188
-rw-r--r--boot/bootmeth_efi_mgr.c86
-rw-r--r--boot/bootmeth_pxe.c186
-rw-r--r--boot/bootmeth_sandbox.c69
-rw-r--r--boot/bootmeth_script.c139
-rw-r--r--boot/bootstd-uclass.c183
-rw-r--r--boot/system_bootdev.c66
-rw-r--r--cmd/Kconfig39
-rw-r--r--cmd/Makefile3
-rw-r--r--cmd/bootdev.c120
-rw-r--r--cmd/bootflow.c404
-rw-r--r--cmd/bootmeth.c113
-rw-r--r--common/usb_storage.c11
-rw-r--r--configs/efi-x86_app32_defconfig1
-rw-r--r--configs/efi-x86_app64_defconfig1
-rw-r--r--configs/rcar3_salvator-x_defconfig1
-rw-r--r--configs/sandbox_defconfig3
-rw-r--r--configs/sandbox_flattree_defconfig3
-rw-r--r--configs/tbs2910_defconfig1
-rw-r--r--doc/develop/bootstd.rst638
-rw-r--r--doc/develop/distro.rst3
-rw-r--r--doc/develop/index.rst1
-rw-r--r--doc/device-tree-bindings/bootdev.txt26
-rw-r--r--doc/device-tree-bindings/bootmeth.txt31
-rw-r--r--doc/device-tree-bindings/bootstd.txt36
-rw-r--r--doc/usage/cmd/bootdev.rst135
-rw-r--r--doc/usage/cmd/bootflow.rst427
-rw-r--r--doc/usage/cmd/bootmeth.rst108
-rw-r--r--doc/usage/index.rst3
-rw-r--r--drivers/block/blk-uclass.c7
-rw-r--r--drivers/core/uclass.c20
-rw-r--r--drivers/mmc/Makefile5
-rw-r--r--drivers/mmc/mmc-uclass.c23
-rw-r--r--drivers/mmc/mmc_bootdev.c62
-rw-r--r--drivers/usb/host/Makefile4
-rw-r--r--drivers/usb/host/usb_bootdev.c61
-rw-r--r--fs/Kconfig2
-rw-r--r--fs/fs.c5
-rw-r--r--fs/sandbox/Kconfig2
-rw-r--r--fs/sandbox/Makefile1
-rw-r--r--fs/sandbox/host_bootdev.c56
-rw-r--r--include/blk.h8
-rw-r--r--include/bootdev.h275
-rw-r--r--include/bootflow.h310
-rw-r--r--include/bootmeth.h234
-rw-r--r--include/bootstd.h80
-rw-r--r--include/distro.h24
-rw-r--r--include/dm/device.h2
-rw-r--r--include/dm/uclass-id.h3
-rw-r--r--include/dm/uclass-internal.h16
-rw-r--r--include/dm/uclass.h6
-rw-r--r--include/env_callback.h7
-rw-r--r--include/fs.h11
-rw-r--r--include/mmc.h12
-rw-r--r--include/test/suites.h2
-rw-r--r--include/vsprintf.h117
-rw-r--r--lib/strto.c23
-rw-r--r--net/Kconfig9
-rw-r--r--net/Makefile1
-rw-r--r--net/eth-uclass.c8
-rw-r--r--net/eth_bootdev.c101
-rw-r--r--test/Makefile1
-rw-r--r--test/boot/Makefile5
-rw-r--r--test/boot/bootdev.c223
-rw-r--r--test/boot/bootflow.c400
-rw-r--r--test/boot/bootmeth.c122
-rw-r--r--test/boot/bootstd_common.c35
-rw-r--r--test/boot/bootstd_common.h27
-rw-r--r--test/cmd_ut.c7
-rw-r--r--test/dm/blk.c6
-rw-r--r--test/dm/core.c17
-rw-r--r--test/dm/fastboot.c4
-rw-r--r--test/print_ut.c40
-rw-r--r--test/py/tests/bootstd/mmc1.img.xzbin0 -> 4448 bytes
-rw-r--r--test/py/tests/test_ut.py103
-rw-r--r--test/str_ut.c72
86 files changed, 7188 insertions, 110 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index 8e49a84bad..212d577eb9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -694,6 +694,27 @@ M: Alper Nebi Yasak <alpernebiyasak@gmail.com>
S: Maintained
F: tools/binman/
+BOOTDEVICE
+M: Simon Glass <sjg@chromium.org>
+S: Maintained
+F: boot/bootdev*.c
+F: boot/bootflow.c
+F: boot/bootmeth*.c
+F: boot/bootstd.c
+F: cmd/bootdev.c
+F: cmd/bootflow.c
+F: doc/develop/bootstd.rst
+F: doc/usage/bootdev.rst
+F: doc/usage/bootflow.rst
+F: doc/usage/bootmeth.rst
+F: drivers/mmc/mmc_bootdev.c
+F: include/bootdev.h
+F: include/bootflow.h
+F: include/bootmeth.h
+F: include/bootstd.h
+F: net/eth_bootdevice.c
+F: test/boot/
+
BTRFS
M: Marek Behun <marek.behun@nic.cz>
R: Qu Wenruo <wqu@suse.com>
diff --git a/arch/sandbox/dts/sandbox.dts b/arch/sandbox/dts/sandbox.dts
index 127f168f02..18fde1c8c6 100644
--- a/arch/sandbox/dts/sandbox.dts
+++ b/arch/sandbox/dts/sandbox.dts
@@ -66,6 +66,10 @@
fake-host-hwaddr = [00 00 66 44 22 00];
};
+ host-fs {
+ compatible = "sandbox,bootdev-host";
+ };
+
i2c_0: i2c@0 {
#address-cells = <1>;
#size-cells = <0>;
diff --git a/arch/sandbox/dts/sandbox.dtsi b/arch/sandbox/dts/sandbox.dtsi
index 826db26fc2..29306ac04d 100644
--- a/arch/sandbox/dts/sandbox.dtsi
+++ b/arch/sandbox/dts/sandbox.dtsi
@@ -19,6 +19,11 @@
#sound-dai-cells = <1>;
};
+ bootstd {
+ compatible = "u-boot,boot-std";
+ filename-prefixes = "./";
+ };
+
buttons {
compatible = "gpio-keys";
diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts
index 5b38ee4a5f..a8a86bc715 100644
--- a/arch/sandbox/dts/test.dts
+++ b/arch/sandbox/dts/test.dts
@@ -74,6 +74,21 @@
};
};
+ bootstd {
+ compatible = "u-boot,boot-std";
+
+ filename-prefixes = "/", "/boot/";
+ bootdev-order = "mmc2", "mmc1";
+
+ syslinux {
+ compatible = "u-boot,distro-syslinux";
+ };
+
+ efi {
+ compatible = "u-boot,distro-efi";
+ };
+ };
+
reboot-mode0 {
compatible = "reboot-mode-gpio";
gpios = <&gpio_c 0 GPIO_ACTIVE_HIGH>, <&gpio_c 1 GPIO_ACTIVE_HIGH>;
@@ -891,10 +906,13 @@
non-removable;
};
+ /* This is used for the bootdev tests */
mmc1 {
compatible = "sandbox,mmc";
+ filename = "mmc1.img";
};
+ /* This is used for the fastboot tests */
mmc0 {
compatible = "sandbox,mmc";
};
diff --git a/boot/Kconfig b/boot/Kconfig
index ec5b956490..4b0802b8c6 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 1b99e6ee33..a70674259c 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 0000000000..1ede933c2f
--- /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 <sjg@chromium.org>
+ */
+
+#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 0000000000..24ba3c3466
--- /dev/null
+++ b/boot/bootflow.c
@@ -0,0 +1,411 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#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 0000000000..c040d5f92b
--- /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 <sjg@chromium.org>
+ */
+
+#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 0000000000..2b41e654ad
--- /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 <sjg@chromium.org>
+ */
+
+#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 0000000000..d5438eb67b
--- /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 <sjg@chromium.org>
+ */
+
+#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 0000000000..a6914466db
--- /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 <sjg@chromium.org>
+ */
+
+#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 0000000000..f1e2b4c776
--- /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 <sjg@chromium.org>
+ */
+
+#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 0000000000..13ec5e95e6
--- /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 <sjg@chromium.org>
+ */
+
+#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 0000000000..d1c3f94003
--- /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 <sjg@chromium.org>
+ */
+
+#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 0000000000..3c6c32ae60
--- /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 <sjg@chromium.org>
+ */
+
+#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 0000000000..432d203478
--- /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 <sjg@chromium.org>
+ */
+
+#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,
+};
diff --git a/cmd/Kconfig b/cmd/Kconfig
index 65517cb69e..2b575a2b42 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -211,6 +211,45 @@ config CMD_BOOTM_PRE_LOAD
This stage allow to check or modify the image provided
to the bootm command.
+config CMD_BOOTDEV
+ bool "bootdev"
+ depends on BOOTSTD
+ default y if BOOTSTD_FULL
+ help
+ Support listing available bootdevs (boot devices) which can provide an
+ OS to boot, as well as showing information about a particular one.
+
+ This command is not necessary for bootstd to work.
+
+config CMD_BOOTFLOW
+ bool "bootflow"
+ depends on BOOTSTD
+ default y
+ help
+ Support scanning for bootflows available with the bootdevs. The
+ bootflows can optionally be booted.
+
+config CMD_BOOTFLOW_FULL
+ bool "bootflow - extract subcommands"
+ depends on BOOTSTD_FULL
+ default y if BOOTSTD_FULL
+ help
+ Add the ability to list the available bootflows, select one and obtain
+ information about it.
+
+ This command is not necessary for bootstd to work.
+
+config CMD_BOOTMETH
+ bool "bootmeth"
+ depends on BOOTSTD
+ default y if BOOTSTD_FULL
+ help
+ Support listing available bootmethds (methods used to boot an
+ Operating System), as well as selecting the order that the bootmeths
+ are used.
+
+ This command is not necessary for bootstd to work.
+
config BOOTM_EFI
bool "Support booting UEFI FIT images"
depends on CMD_BOOTEFI && CMD_BOOTM && FIT
diff --git a/cmd/Makefile b/cmd/Makefile
index ede634d731..5e43a1e022 100644
--- a/cmd/Makefile
+++ b/cmd/Makefile
@@ -19,6 +19,9 @@ obj-$(CONFIG_CMD_AB_SELECT) += ab_select.o
obj-$(CONFIG_CMD_ADC) += adc.o
obj-$(CONFIG_CMD_ARMFLASH) += armflash.o
obj-$(CONFIG_HAVE_BLOCK_DEVICE) += blk_common.o
+obj-$(CONFIG_CMD_BOOTDEV) += bootdev.o
+obj-$(CONFIG_CMD_BOOTFLOW) += bootflow.o
+obj-$(CONFIG_CMD_BOOTMETH) += bootmeth.o
obj-$(CONFIG_CMD_SOURCE) += source.o
obj-$(CONFIG_CMD_BCB) += bcb.o
obj-$(CONFIG_CMD_BDI) += bdinfo.o
diff --git a/cmd/bootdev.c b/cmd/bootdev.c
new file mode 100644
index 0000000000..ecd797c050
--- /dev/null
+++ b/cmd/bootdev.c
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * 'bootdev' command
+ *
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <common.h>
+#include <bootdev.h>
+#include <bootflow.h>
+#include <bootstd.h>
+#include <command.h>
+#include <dm.h>
+#include <dm/device-internal.h>
+#include <dm/uclass-internal.h>
+
+static int bootdev_check_state(struct bootstd_priv **stdp)
+{
+ struct bootstd_priv *std;
+ int ret;
+
+ ret = bootstd_get_priv(&std);
+ if (ret)
+ return ret;
+ if (!std->cur_bootdev) {
+ printf("Please use 'bootdev select' first\n");
+ return -ENOENT;
+ }
+ *stdp = std;
+
+ return 0;
+}
+
+static int do_bootdev_list(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ bool probe;
+
+ probe = argc >= 2 && !strcmp(argv[1], "-p");
+ bootdev_list(probe);
+
+ return 0;
+}
+
+static int do_bootdev_select(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ struct bootstd_priv *std;
+ struct udevice *dev;
+ int ret;
+
+ ret = bootstd_get_priv(&std);
+ if (ret)
+ return CMD_RET_FAILURE;
+ if (argc < 2) {
+ std->cur_bootdev = NULL;
+ return 0;
+ }
+ if (bootdev_find_by_any(argv[1], &dev))
+ return CMD_RET_FAILURE;
+
+ std->cur_bootdev = dev;
+
+ return 0;
+}
+
+static int do_bootdev_info(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ struct bootstd_priv *priv;
+ struct bootflow *bflow;
+ int ret, i, num_valid;
+ struct udevice *dev;
+ bool probe;
+
+ probe = argc >= 2 && !strcmp(argv[1], "-p");
+
+ ret = bootdev_check_state(&priv);
+ if (ret)
+ return CMD_RET_FAILURE;
+
+ dev = priv->cur_bootdev;
+
+ /* Count the number of bootflows, including how many are valid*/
+ num_valid = 0;
+ for (ret = bootdev_first_bootflow(dev, &bflow), i = 0;
+ !ret;
+ ret = bootdev_next_bootflow(&bflow), i++)
+ num_valid += bflow->state == BOOTFLOWST_READY;
+
+ /*
+ * Prove the device, if requested, otherwise assume that there is no
+ * error
+ */
+ ret = 0;
+ if (probe)
+ ret = device_probe(dev);
+
+ printf("Name: %s\n", dev->name);
+ printf("Sequence: %d\n", dev_seq(dev));
+ printf("Status: %s\n", ret ? simple_itoa(ret) : device_active(dev) ?
+ "Probed" : "OK");
+ printf("Uclass: %s\n", dev_get_uclass_name(dev_get_parent(dev)));
+ printf("Bootflows: %d (%d valid)\n", i, num_valid);
+
+ return 0;
+}
+
+#ifdef CONFIG_SYS_LONGHELP
+static char bootdev_help_text[] =
+ "list [-p] - list all available bootdevs (-p to probe)\n"
+ "bootdev select <bd> - select a bootdev by name | label | seq\n"
+ "bootdev info [-p] - show information about a bootdev (-p to probe)";
+#endif
+
+U_BOOT_CMD_WITH_SUBCMDS(bootdev, "Boot devices", bootdev_help_text,
+ U_BOOT_SUBCMD_MKENT(list, 2, 1, do_bootdev_list),
+ U_BOOT_SUBCMD_MKENT(select, 2, 1, do_bootdev_select),
+ U_BOOT_SUBCMD_MKENT(info, 2, 1, do_bootdev_info));
diff --git a/cmd/bootflow.c b/cmd/bootflow.c
new file mode 100644
index 0000000000..af4b9c3732
--- /dev/null
+++ b/cmd/bootflow.c
@@ -0,0 +1,404 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * 'bootflow' command
+ *
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <common.h>
+#include <bootdev.h>
+#include <bootflow.h>
+#include <bootstd.h>
+#include <command.h>
+#include <console.h>
+#include <dm.h>
+#include <mapmem.h>
+
+/**
+ * report_bootflow_err() - Report where a bootflow failed
+ *
+ * When a bootflow does not make it to the 'loaded' state, something went wrong.
+ * Print a helpful message if there is an error
+ *
+ * @bflow: Bootflow to process
+ * @err: Error code (0 if none)
+ */
+static void report_bootflow_err(struct bootflow *bflow, int err)
+{
+ if (!err)
+ return;
+
+ /* Indent out to 'Method' */
+ printf(" ** ");
+
+ switch (bflow->state) {
+ case BOOTFLOWST_BASE:
+ printf("No media/partition found");
+ break;
+ case BOOTFLOWST_MEDIA:
+ printf("No partition found");
+ break;
+ case BOOTFLOWST_PART:
+ printf("No filesystem found");
+ break;
+ case BOOTFLOWST_FS:
+ printf("File not found");
+ break;
+ case BOOTFLOWST_FILE:
+ printf("File cannot be loaded");
+ break;
+ case BOOTFLOWST_READY:
+ printf("Ready");
+ break;
+ case BOOTFLOWST_COUNT:
+ break;
+ }
+
+ printf(", err=%d\n", err);
+}
+
+/**
+ * show_bootflow() - Show the status of a bootflow
+ *
+ * @seq: Bootflow index
+ * @bflow: Bootflow to show
+ * @errors: True to show the error received, if any
+ */
+static void show_bootflow(int index, struct bootflow *bflow, bool errors)
+{
+ printf("%3x %-11s %-6s %-9.9s %4x %-25.25s %s\n", index,
+ bflow->method->name, bootflow_state_get_name(bflow->state),
+ dev_get_uclass_name(dev_get_parent(bflow->dev)), bflow->part,
+ bflow->name, bflow->fname);
+ if (errors)
+ report_bootflow_err(bflow, bflow->err);
+}
+
+static void show_header(void)
+{
+ printf("Seq Method State Uclass Part Name Filename\n");
+ printf("--- ----------- ------ -------- ---- ------------------------ ----------------\n");
+}
+
+static void show_footer(int count, int num_valid)
+{
+ printf("--- ----------- ------ -------- ---- ------------------------ ----------------\n");
+ printf("(%d bootflow%s, %d valid)\n", count, count != 1 ? "s" : "",
+ num_valid);
+}
+
+static int do_bootflow_scan(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ struct bootstd_priv *std;
+ struct bootflow_iter iter;
+ struct udevice *dev;
+ struct bootflow bflow;
+ bool all = false, boot = false, errors = false, list = false;
+ int num_valid = 0;
+ bool has_args;
+ int ret, i;
+ int flags;
+
+ ret = bootstd_get_priv(&std);
+ if (ret)
+ return CMD_RET_FAILURE;
+ dev = std->cur_bootdev;
+
+ has_args = argc > 1 && *argv[1] == '-';
+ if (IS_ENABLED(CONFIG_CMD_BOOTFLOW_FULL)) {
+ if (has_args) {
+ all = strchr(argv[1], 'a');
+ boot = strchr(argv[1], 'b');
+ errors = strchr(argv[1], 'e');
+ list = strchr(argv[1], 'l');
+ argc--;
+ argv++;
+ }
+ if (argc > 1) {
+ const char *label = argv[1];
+
+ if (bootdev_find_by_any(label, &dev))
+ return CMD_RET_FAILURE;
+ }
+ } else {
+ if (has_args) {
+ printf("Flags not supported: enable CONFIG_BOOTFLOW_FULL\n");
+ return CMD_RET_USAGE;
+ }
+ boot = true;
+ }
+
+ std->cur_bootflow = NULL;
+
+ flags = 0;
+ if (list)
+ flags |= BOOTFLOWF_SHOW;
+ if (all)
+ flags |= BOOTFLOWF_ALL;
+
+ /*
+ * If we have a device, just scan for bootflows attached to that device
+ */
+ if (IS_ENABLED(CONFIG_CMD_BOOTFLOW_FULL) && dev) {
+ if (list) {
+ printf("Scanning for bootflows in bootdev '%s'\n",
+ dev->name);
+ show_header();
+ }
+ bootdev_clear_bootflows(dev);
+ for (i = 0,
+ ret = bootflow_scan_bootdev(dev, &iter, flags, &bflow);
+ i < 1000 && ret != -ENODEV;
+ i++, ret = bootflow_scan_next(&iter, &bflow)) {
+ bflow.err = ret;
+ if (!ret)
+ num_valid++;
+ ret = bootdev_add_bootflow(&bflow);
+ if (ret) {
+ printf("Out of memory\n");
+ return CMD_RET_FAILURE;
+ }
+ if (list)
+ show_bootflow(i, &bflow, errors);
+ if (boot && !bflow.err)
+ bootflow_run_boot(&iter, &bflow);
+ }
+ } else {
+ if (list) {
+ printf("Scanning for bootflows in all bootdevs\n");
+ show_header();
+ }
+ bootstd_clear_glob();
+
+ for (i = 0,
+ ret = bootflow_scan_first(&iter, flags, &bflow);
+ i < 1000 && ret != -ENODEV;
+ i++, ret = bootflow_scan_next(&iter, &bflow)) {
+ bflow.err = ret;
+ if (!ret)
+ num_valid++;
+ ret = bootdev_add_bootflow(&bflow);
+ if (ret) {
+ printf("Out of memory\n");
+ return CMD_RET_FAILURE;
+ }
+ if (list)
+ show_bootflow(i, &bflow, errors);
+ if (boot && !bflow.err)
+ bootflow_run_boot(&iter, &bflow);
+ }
+ }
+ bootflow_iter_uninit(&iter);
+ if (list)
+ show_footer(i, num_valid);
+
+ return 0;
+}
+
+#ifdef CONFIG_CMD_BOOTFLOW_FULL
+static int do_bootflow_list(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ struct bootstd_priv *std;
+ struct udevice *dev;
+ struct bootflow *bflow;
+ int num_valid = 0;
+ bool errors = false;
+ int ret, i;
+
+ if (argc > 1 && *argv[1] == '-')
+ errors = strchr(argv[1], 'e');
+
+ ret = bootstd_get_priv(&std);
+ if (ret)
+ return CMD_RET_FAILURE;
+ dev = std->cur_bootdev;
+
+ /* If we have a device, just list bootflows attached to that device */
+ if (dev) {
+ printf("Showing bootflows for bootdev '%s'\n", dev->name);
+ show_header();
+ for (ret = bootdev_first_bootflow(dev, &bflow), i = 0;
+ !ret;
+ ret = bootdev_next_bootflow(&bflow), i++) {
+ num_valid += bflow->state == BOOTFLOWST_READY;
+ show_bootflow(i, bflow, errors);
+ }
+ } else {
+ printf("Showing all bootflows\n");
+ show_header();
+ for (ret = bootflow_first_glob(&bflow), i = 0;
+ !ret;
+ ret = bootflow_next_glob(&bflow), i++) {
+ num_valid += bflow->state == BOOTFLOWST_READY;
+ show_bootflow(i, bflow, errors);
+ }
+ }
+ show_footer(i, num_valid);
+
+ return 0;
+}
+
+static int do_bootflow_select(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ struct bootstd_priv *std;
+ struct bootflow *bflow, *found;
+ struct udevice *dev;
+ const char *name;
+ char *endp;
+ int seq, i;
+ int ret;
+
+ ret = bootstd_get_priv(&std);
+ if (ret)
+ return CMD_RET_FAILURE;
+;
+ if (argc < 2) {
+ std->cur_bootflow = NULL;
+ return 0;
+ }
+ dev = std->cur_bootdev;
+
+ name = argv[1];
+ seq = simple_strtol(name, &endp, 16);
+ found = NULL;
+
+ /*
+ * If we have a bootdev device, only allow selection of bootflows
+ * attached to that device
+ */
+ if (dev) {
+ for (ret = bootdev_first_bootflow(dev, &bflow), i = 0;
+ !ret;
+ ret = bootdev_next_bootflow(&bflow), i++) {
+ if (*endp ? !strcmp(bflow->name, name) : i == seq) {
+ found = bflow;
+ break;
+ }
+ }
+ } else {
+ for (ret = bootflow_first_glob(&bflow), i = 0;
+ !ret;
+ ret = bootflow_next_glob(&bflow), i++) {
+ if (*endp ? !strcmp(bflow->name, name) : i == seq) {
+ found = bflow;
+ break;
+ }
+ }
+ }
+
+ if (!found) {
+ printf("Cannot find bootflow '%s' ", name);
+ if (dev)
+ printf("in bootdev '%s' ", dev->name);
+ printf("(err=%d)\n", ret);
+ return CMD_RET_FAILURE;
+ }
+ std->cur_bootflow = found;
+
+ return 0;
+}
+
+static int do_bootflow_info(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ struct bootstd_priv *std;
+ struct bootflow *bflow;
+ bool dump = false;
+ int ret;
+
+ if (argc > 1 && *argv[1] == '-')
+ dump = strchr(argv[1], 'd');
+
+ ret = bootstd_get_priv(&std);
+ if (ret)
+ return CMD_RET_FAILURE;
+
+ if (!std->cur_bootflow) {
+ printf("No bootflow selected\n");
+ return CMD_RET_FAILURE;
+ }
+ bflow = std->cur_bootflow;
+
+ printf("Name: %s\n", bflow->name);
+ printf("Device: %s\n", bflow->dev->name);
+ printf("Block dev: %s\n", bflow->blk ? bflow->blk->name : "(none)");
+ printf("Method: %s\n", bflow->method->name);
+ printf("State: %s\n", bootflow_state_get_name(bflow->state));
+ printf("Partition: %d\n", bflow->part);
+ printf("Subdir: %s\n", bflow->subdir ? bflow->subdir : "(none)");
+ printf("Filename: %s\n", bflow->fname);
+ printf("Buffer: %lx\n", (ulong)map_to_sysmem(bflow->buf));
+ printf("Size: %x (%d bytes)\n", bflow->size, bflow->size);
+ printf("Error: %d\n", bflow->err);
+ if (dump && bflow->buf) {
+ /* Set some sort of maximum on the size */
+ int size = min(bflow->size, 10 << 10);
+ int i;
+
+ printf("Contents:\n\n");
+ for (i = 0; i < size; i++) {
+ putc(bflow->buf[i]);
+ if (!(i % 128) && ctrlc()) {
+ printf("...interrupted\n");
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int do_bootflow_boot(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ struct bootstd_priv *std;
+ struct bootflow *bflow;
+ int ret;
+
+ ret = bootstd_get_priv(&std);
+ if (ret)
+ return CMD_RET_FAILURE;
+
+ /*
+ * Require a current bootflow. Users can use 'bootflow scan -b' to
+ * automatically scan and boot, if needed.
+ */
+ if (!std->cur_bootflow) {
+ printf("No bootflow selected\n");
+ return CMD_RET_FAILURE;
+ }
+ bflow = std->cur_bootflow;
+ ret = bootflow_run_boot(NULL, bflow);
+ if (ret)
+ return CMD_RET_FAILURE;
+
+ return 0;
+}
+#endif /* CONFIG_CMD_BOOTFLOW_FULL */
+
+#ifdef CONFIG_SYS_LONGHELP
+static char bootflow_help_text[] =
+#ifdef CONFIG_CMD_BOOTFLOW_FULL
+ "scan [-abel] [bdev] - scan for valid bootflows (-l list, -a all, -e errors, -b boot)\n"
+ "bootflow list [-e] - list scanned bootflows (-e errors)\n"
+ "bootflow select [<num>|<name>] - select a bootflow\n"
+ "bootflow info [-d] - show info on current bootflow (-d dump bootflow)\n"
+ "bootflow boot - boot current bootflow (or first available if none selected)";
+#else
+ "scan - boot first available bootflow\n";
+#endif
+#endif /* CONFIG_SYS_LONGHELP */
+
+U_BOOT_CMD_WITH_SUBCMDS(bootflow, "Boot flows", bootflow_help_text,
+ U_BOOT_SUBCMD_MKENT(scan, 3, 1, do_bootflow_scan),
+#ifdef CONFIG_CMD_BOOTFLOW_FULL
+ U_BOOT_SUBCMD_MKENT(list, 2, 1, do_bootflow_list),
+ U_BOOT_SUBCMD_MKENT(select, 2, 1, do_bootflow_select),
+ U_BOOT_SUBCMD_MKENT(info, 2, 1, do_bootflow_info),
+ U_BOOT_SUBCMD_MKENT(boot, 1, 1, do_bootflow_boot)
+#endif
+);
diff --git a/cmd/bootmeth.c b/cmd/bootmeth.c
new file mode 100644
index 0000000000..c9a27fe8ac
--- /dev/null
+++ b/cmd/bootmeth.c
@@ -0,0 +1,113 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * 'bootmeth' command
+ *
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <common.h>
+#include <bootdev.h>
+#include <bootmeth.h>
+#include <bootstd.h>
+#include <command.h>
+#include <dm.h>
+#include <malloc.h>
+#include <dm/uclass-internal.h>
+
+static int do_bootmeth_list(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ struct bootstd_priv *std;
+ struct udevice *dev;
+ bool use_order;
+ bool all = false;
+ int ret;
+ int i;
+
+ if (argc > 1 && *argv[1] == '-') {
+ all = strchr(argv[1], 'a');
+ argc--;
+ argv++;
+ }
+
+ ret = bootstd_get_priv(&std);
+ if (ret) {
+ printf("Cannot get bootstd (err=%d)\n", ret);
+ return CMD_RET_FAILURE;
+ }
+
+ printf("Order Seq Name Description\n");
+ printf("----- --- ------------------ ------------------\n");
+
+ /*
+ * Use the ordering if we have one, so long as we are not trying to list
+ * all bootmethds
+ */
+ use_order = std->bootmeth_count && !all;
+ if (use_order)
+ dev = std->bootmeth_order[0];
+ else
+ ret = uclass_find_first_device(UCLASS_BOOTMETH, &dev);
+
+ for (i = 0; dev;) {
+ struct bootmeth_uc_plat *ucp = dev_get_uclass_plat(dev);
+ int order = i;
+
+ /*
+ * With the -a flag we may list bootdevs that are not in the
+ * ordering. Find their place in the order
+ */
+ if (all && std->bootmeth_count) {
+ int j;
+
+ /* Find the position of this bootmeth in the order */
+ order = -1;
+ for (j = 0; j < std->bootmeth_count; j++) {
+ if (std->bootmeth_order[j] == dev)
+ order = j;
+ }
+ }
+
+ if (order == -1)
+ printf("%5s", "-");
+ else
+ printf("%5x", order);
+ printf(" %3x %-19.19s %s\n", dev_seq(dev), dev->name,
+ ucp->desc);
+ i++;
+ if (use_order)
+ dev = std->bootmeth_order[i];
+ else
+ uclass_find_next_device(&dev);
+ }
+ printf("----- --- ------------------ ------------------\n");
+ printf("(%d bootmeth%s)\n", i, i != 1 ? "s" : "");
+
+ return 0;
+}
+
+static int do_bootmeth_order(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ int ret;
+
+ ret = bootmeth_set_order(argv[1]);
+ if (ret) {
+ printf("Failed (err=%d)\n", ret);
+ return CMD_RET_FAILURE;
+ }
+ env_set("bootmeths", argv[1]);
+
+ return 0;
+}
+
+#ifdef CONFIG_SYS_LONGHELP
+static char bootmeth_help_text[] =
+ "list [-a] - list available bootmeths (-a all)\n"
+ "bootmeth order [<bd> ...] - select bootmeth order / subset to use";
+#endif
+
+U_BOOT_CMD_WITH_SUBCMDS(bootmeth, "Boot methods", bootmeth_help_text,
+ U_BOOT_SUBCMD_MKENT(list, 2, 1, do_bootmeth_list),
+ U_BOOT_SUBCMD_MKENT(order, CONFIG_SYS_MAXARGS, 1, do_bootmeth_order));
diff --git a/common/usb_storage.c b/common/usb_storage.c
index 291728f37e..eaa31374ef 100644
--- a/common/usb_storage.c
+++ b/common/usb_storage.c
@@ -34,6 +34,7 @@
#include <common.h>
#include <blk.h>
+#include <bootdev.h>
#include <command.h>
#include <dm.h>
#include <errno.h>
@@ -243,6 +244,16 @@ static int usb_stor_probe_device(struct usb_device *udev)
ret = blk_probe_or_unbind(dev);
if (ret)
return ret;
+
+ ret = bootdev_setup_sibling_blk(dev, "usb_bootdev");
+ if (ret) {
+ int ret2;
+
+ ret2 = device_unbind(dev);
+ if (ret2)
+ return log_msg_ret("bootdev", ret2);
+ return log_msg_ret("bootdev", ret);
+ }
}
#else
/* We don't have space to even probe if we hit the maximum */
diff --git a/configs/efi-x86_app32_defconfig b/configs/efi-x86_app32_defconfig
index 228643a1bd..7a723c136c 100644
--- a/configs/efi-x86_app32_defconfig
+++ b/configs/efi-x86_app32_defconfig
@@ -8,6 +8,7 @@ CONFIG_VENDOR_EFI=y
CONFIG_TARGET_EFI_APP32=y
CONFIG_DEBUG_UART=y
CONFIG_FIT=y
+# CONFIG_BOOTSTD is not set
CONFIG_SHOW_BOOT_PROGRESS=y
CONFIG_USE_BOOTARGS=y
CONFIG_BOOTARGS="root=/dev/sdb3 init=/sbin/init rootwait ro"
diff --git a/configs/efi-x86_app64_defconfig b/configs/efi-x86_app64_defconfig
index 1ed2f13050..98f91d8116 100644
--- a/configs/efi-x86_app64_defconfig
+++ b/configs/efi-x86_app64_defconfig
@@ -8,6 +8,7 @@ CONFIG_VENDOR_EFI=y
CONFIG_TARGET_EFI_APP64=y
CONFIG_DEBUG_UART=y
CONFIG_FIT=y
+# CONFIG_BOOTSTD is not set
CONFIG_SHOW_BOOT_PROGRESS=y
CONFIG_USE_BOOTARGS=y
CONFIG_BOOTARGS="root=/dev/sdb3 init=/sbin/init rootwait ro"
diff --git a/configs/rcar3_salvator-x_defconfig b/configs/rcar3_salvator-x_defconfig
index f333291aa4..6ef62ab4cb 100644
--- a/configs/rcar3_salvator-x_defconfig
+++ b/configs/rcar3_salvator-x_defconfig
@@ -14,6 +14,7 @@ CONFIG_SYS_LOAD_ADDR=0x58000000
CONFIG_LTO=y
CONFIG_REMAKE_ELF=y
CONFIG_FIT=y
+# CONFIG_BOOTSTD is not set
CONFIG_SUPPORT_RAW_INITRD=y
CONFIG_SYS_MONITOR_BASE=0x00000000
CONFIG_USE_BOOTARGS=y
diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index 14d7af4db2..fe8ea4626d 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -28,7 +28,7 @@ CONFIG_AUTOBOOT_STOP_STR_CRYPT="$5$rounds=640000$HrpE65IkB8CM5nCL$BKT3QdF98Bo8fJ
CONFIG_IMAGE_PRE_LOAD=y
CONFIG_IMAGE_PRE_LOAD_SIG=y
CONFIG_CONSOLE_RECORD=y
-CONFIG_CONSOLE_RECORD_OUT_SIZE=0x1000
+CONFIG_CONSOLE_RECORD_OUT_SIZE=0x6000
CONFIG_PRE_CONSOLE_BUFFER=y
CONFIG_LOG=y
CONFIG_DISPLAY_BOARDINFO_LATE=y
@@ -120,6 +120,7 @@ CONFIG_ENV_IS_IN_EXT4=y
CONFIG_ENV_EXT4_INTERFACE="host"
CONFIG_ENV_EXT4_DEVICE_AND_PART="0:0"
CONFIG_ENV_IMPORT_FDT=y
+# CONFIG_BOOTDEV_ETH is not set
CONFIG_BOOTP_SEND_HOSTNAME=y
CONFIG_NETCONSOLE=y
CONFIG_IP_DEFRAG=y
diff --git a/configs/sandbox_flattree_defconfig b/configs/sandbox_flattree_defconfig
index ded16af9f5..80a4be47eb 100644
--- a/configs/sandbox_flattree_defconfig
+++ b/configs/sandbox_flattree_defconfig
@@ -17,7 +17,7 @@ CONFIG_BOOTSTAGE_FDT=y
CONFIG_BOOTSTAGE_STASH=y
CONFIG_BOOTSTAGE_STASH_SIZE=0x4096
CONFIG_CONSOLE_RECORD=y
-CONFIG_CONSOLE_RECORD_OUT_SIZE=0x1000
+CONFIG_CONSOLE_RECORD_OUT_SIZE=0x6000
CONFIG_DISPLAY_BOARDINFO_LATE=y
CONFIG_CMD_CPU=y
CONFIG_CMD_LICENSE=y
@@ -72,6 +72,7 @@ CONFIG_ENV_IS_NOWHERE=y
CONFIG_ENV_IS_IN_EXT4=y
CONFIG_ENV_EXT4_INTERFACE="host"
CONFIG_ENV_EXT4_DEVICE_AND_PART="0:0"
+# CONFIG_BOOTDEV_ETH is not set
CONFIG_BOOTP_SEND_HOSTNAME=y
CONFIG_NETCONSOLE=y
CONFIG_IP_DEFRAG=y
diff --git a/configs/tbs2910_defconfig b/configs/tbs2910_defconfig
index 60a816059a..9f7642f25c 100644
--- a/configs/tbs2910_defconfig
+++ b/configs/tbs2910_defconfig
@@ -17,6 +17,7 @@ CONFIG_SYS_MEMTEST_END=0x2f400000
CONFIG_LTO=y
CONFIG_HAS_BOARD_SIZE_LIMIT=y
CONFIG_BOARD_SIZE_LIMIT=392192
+# CONFIG_BOOTSTD is not set
CONFIG_SUPPORT_RAW_INITRD=y
CONFIG_BOOTDELAY=3
CONFIG_USE_BOOTCOMMAND=y
diff --git a/doc/develop/bootstd.rst b/doc/develop/bootstd.rst
new file mode 100644
index 0000000000..5e9c0d282b
--- /dev/null
+++ b/doc/develop/bootstd.rst
@@ -0,0 +1,638 @@
+.. SPDX-License-Identifier: GPL-2.0+:
+
+U-Boot Standard Boot
+====================
+
+Introduction
+------------
+
+Standard boot provides a built-in way for U-Boot to automatically boot
+an Operating System without custom scripting and other customisation. It
+introduces the following concepts:
+
+ - bootdev - a device which can hold or access a distro (e.g. MMC, Ethernet)
+ - bootmeth - a method to scan a bootdev to find bootflows (e.g. distro boot)
+ - bootflow - a description of how to boot (provided by the distro)
+
+For Linux, the distro (Linux distribution, e.g. Debian, Fedora) is responsible
+for creating a bootflow for each kernel combination that it wants to offer.
+These bootflows are stored on media so they can be discovered by U-Boot. This
+feature is typically called `distro boot` (see :doc:`distro`) because it is
+a way for distributions to boot on any hardware.
+
+Traditionally U-Boot has relied on scripts to implement this feature. See
+disto_boodcmd_ for details. This is done because U-Boot has no native support
+for scanning devices. While the scripts work remarkably well, they can be hard
+to understand and extend, and the feature does not include tests. They are also
+making it difficult to move away from ad-hoc CONFIGs, since they are implemented
+using the environment and a lot of #defines.
+
+Standard boot is a generalisation of distro boot. It provides a more built-in
+way to boot with U-Boot. The feature is extensible to different Operating
+Systems (such as Chromium OS) and devices (beyond just block and network
+devices). It supports EFI boot and EFI bootmgr too.
+
+
+Bootflow
+--------
+
+A bootflow is a file that describes how to boot a distro. Conceptually there can
+be different formats for that file but at present U-Boot only supports the
+BootLoaderSpec_ format. which looks something like this::
+
+ menu autoboot Welcome to Fedora-Workstation-armhfp-31-1.9. Automatic boot in # second{,s}. Press a key for options.
+ menu title Fedora-Workstation-armhfp-31-1.9 Boot Options.
+ menu hidden
+
+ label Fedora-Workstation-armhfp-31-1.9 (5.3.7-301.fc31.armv7hl)
+ kernel /vmlinuz-5.3.7-301.fc31.armv7hl
+ append ro root=UUID=9732b35b-4cd5-458b-9b91-80f7047e0b8a rhgb quiet LANG=en_US.UTF-8 cma=192MB cma=256MB
+ fdtdir /dtb-5.3.7-301.fc31.armv7hl/
+ initrd /initramfs-5.3.7-301.fc31.armv7hl.img
+
+As you can see it specifies a kernel, a ramdisk (initrd) and a directory from
+which to load devicetree files. The details are described in disto_boodcmd_.
+
+The bootflow is provided by the distro. It is not part of U-Boot. U-Boot's job
+is simply to interpret the file and carry out the instructions. This allows
+distros to boot on essentially any device supported by U-Boot.
+
+Typically the first available bootflow is selected and booted. If that fails,
+then the next one is tried.
+
+
+Bootdev
+-------
+
+Where does U-Boot find the media that holds the operating systems? That is the
+job of bootdev. A bootdev is simply a layer on top of a media device (such as
+MMC, NVMe). The bootdev accesses the device, including partitions and
+filesystems that might contain things related to an operating system.
+
+For example, an MMC bootdev provides access to the individual partitions on the
+MMC device. It scans through these to find filesystems, then provides a list of
+these for consideration.
+
+
+Bootmeth
+--------
+
+Once the list of filesystems is provided, how does U-Boot find the bootflow
+files in these filesystems. That is the job of bootmeth. Each boot method has
+its own way of doing this.
+
+For example, the distro bootmeth simply looks through the provided filesystem
+for a file called `extlinux/extlinux.conf`. This files constitutes a bootflow.
+If the distro bootmeth is used on multiple partitions it may produce multiple
+bootflows.
+
+Note: it is possible to have a bootmeth that uses a partition or a whole device
+directly, but it is more common to use a filesystem.
+
+
+Boot process
+------------
+
+U-Boot tries to use the 'lazy init' approach whereever possible and distro boot
+is no exception. The algorithm is::
+
+ while (get next bootdev)
+ while (get next bootmeth)
+ while (get next bootflow)
+ try to boot it
+
+So U-Boot works its way through the bootdevs, trying each bootmeth in turn to
+obtain bootflows, until it either boots or exhausts the available options.
+
+Instead of 500 lines of #defines and a 4KB boot script, all that is needed is
+the following command::
+
+ bootflow scan -lb
+
+which scans for available bootflows, optionally listing each find it finds (-l)
+and trying to boot it (-b).
+
+
+Controlling ordering
+--------------------
+
+Several options are available to control the ordering of boot scanning:
+
+
+boot_targets
+~~~~~~~~~~~~
+
+This environment variable can be used to control the list of bootdevs searched
+and their ordering, for example::
+
+ setenv boot_targets "mmc0 mmc1 usb pxe"
+
+Entries may be removed or re-ordered in this list to affect the boot order. If
+the variable is empty, the default ordering is used, based on the priority of
+bootdevs and their sequence numbers.
+
+
+bootmeths
+~~~~~~~~~
+
+This environment variable can be used to control the list of bootmeths used and
+their ordering for example::
+
+ setenv bootmeths "syslinux efi"
+
+Entries may be removed or re-ordered in this list to affect the order the
+bootmeths are tried on each bootdev. If the variable is empty, the default
+ordering is used, based on the bootmeth sequence numbers, which can be
+controlled by aliases.
+
+The :ref:`usage/cmd/bootmeth:bootmeth command` (`bootmeth order`) operates in
+the same way as setting this variable.
+
+
+Bootdev uclass
+--------------
+
+The bootdev uclass provides an simple API call to obtain a bootflows from a
+device::
+
+ int bootdev_get_bootflow(struct udevice *dev, struct bootflow_iter *iter,
+ struct bootflow *bflow);
+
+This takes a iterator which indicates the bootdev, partition and bootmeth to
+use. It returns a bootflow. This is the core of the bootdev implementation. The
+bootdev drivers that implement this differ depending on the media they are
+reading from, but each is responsible for returning a valid bootflow if
+available.
+
+A helper called `bootdev_find_in_blk()` makes it fairly easy to implement this
+function for each media device uclass, in a few lines of code.
+
+
+Bootdev drivers
+---------------
+
+A bootdev driver is typically fairly simple. Here is one for mmc::
+
+ static int mmc_get_bootflow(struct udevice *dev, struct bootflow_iter *iter,
+ struct bootflow *bflow)
+ {
+ struct udevice *mmc_dev = dev_get_parent(dev);
+ struct udevice *blk;
+ int ret;
+
+ ret = mmc_get_blk(mmc_dev, &blk);
+ /*
+ * 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;
+ }
+
+ static int mmc_bootdev_bind(struct udevice *dev)
+ {
+ struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev);
+
+ ucp->prio = BOOTDEVP_0_INTERNAL_FAST;
+
+ return 0;
+ }
+
+ struct bootdev_ops mmc_bootdev_ops = {
+ .get_bootflow = mmc_get_bootflow,
+ };
+
+ static const struct udevice_id mmc_bootdev_ids[] = {
+ { .compatible = "u-boot,bootdev-mmc" },
+ { }
+ };
+
+ U_BOOT_DRIVER(mmc_bootdev) = {
+ .name = "mmc_bootdev",
+ .id = UCLASS_BOOTDEV,
+ .ops = &mmc_bootdev_ops,
+ .bind = mmc_bootdev_bind,
+ .of_match = mmc_bootdev_ids,
+ };
+
+The implementation of the `get_bootflow()` method is simply to obtain the
+block device and call a bootdev helper function to do the rest. The
+implementation of `bootdev_find_in_blk()` checks the partition table, and
+attempts to read a file from a filesystem on the partition number given by the
+`@iter->part` parameter.
+
+Each bootdev has a priority, which indicates the order in which it is used.
+Faster bootdevs are used first, since they are more likely to be able to boot
+the device quickly.
+
+
+Device hierarchy
+----------------
+
+A bootdev device is a child of the media device. In this example, you can see
+that the bootdev is a sibling of the block device and both are children of
+media device::
+
+ mmc 0 [ + ] bcm2835-sdhost | |-- mmc@7e202000
+ blk 0 [ + ] mmc_blk | | |-- mmc@7e202000.blk
+ bootdev 0 [ ] mmc_bootdev | | `-- mmc@7e202000.bootdev
+ mmc 1 [ + ] sdhci-bcm2835 | |-- sdhci@7e300000
+ blk 1 [ ] mmc_blk | | |-- sdhci@7e300000.blk
+ bootdev 1 [ ] mmc_bootdev | | `-- sdhci@7e300000.bootdev
+
+The bootdev device is typically created automatically in the media uclass'
+`post_bind()` method by calling `bootdev_setup_for_dev()`. The code typically
+something like this::
+
+ ret = bootdev_setup_for_dev(dev, "eth_bootdev");
+ if (ret)
+ return log_msg_ret("bootdev", ret);
+
+Here, `eth_bootdev` is the name of the Ethernet bootdev driver and `dev`
+is the ethernet device. This function is safe to call even if standard boot is
+not enabled, since it does nothing in that case. It can be added to all uclasses
+which implement suitable media.
+
+
+The bootstd device
+------------------
+
+Standard boot requires a single instance of the bootstd device to make things
+work. This includes global information about the state of standard boot. See
+`struct bootstd_priv` for this structure, accessed with `bootstd_get_priv()`.
+
+Within the devicetree, if you add bootmeth devices or a system bootdev, they
+should be children of the bootstd device. See `arch/sandbox/dts/test.dts` for
+an example of this.
+
+
+The system bootdev
+------------------
+
+Some bootmeths don't operate on individual bootdevs, but on the whole system.
+For example, the EFI boot manager does its own device scanning and does not
+make use of the bootdev devices. Such bootmeths can make use of the system
+bootdev, typically considered last, after everything else has been tried.
+
+
+.. _`Automatic Devices`:
+
+Automatic devices
+-----------------
+
+It is possible to define all the required devices in the devicetree manually,
+but it is not necessary. The bootstd uclass includes a `dm_scan_other()`
+function which creates the bootstd device if not found. If no bootmeth devices
+are found at all, it creates one for each available bootmeth driver as well as a
+system bootdev.
+
+If your devicetree has any bootmeth device it must have all of them that you
+want to use, as well as the system bootdev if needed, since no bootmeth devices
+will be created automatically in that case.
+
+
+Using devicetree
+----------------
+
+If a bootdev is complicated or needs configuration information, it can be
+added to the devicetree as a child of the media device. For example, imagine a
+bootdev which reads a bootflow from SPI flash. The devicetree fragment might
+look like this::
+
+ spi@0 {
+ flash@0 {
+ reg = <0>;
+ compatible = "spansion,m25p16", "jedec,spi-nor";
+ spi-max-frequency = <40000000>;
+
+ bootdev {
+ compatible = "u-boot,sf-bootdev";
+ offset = <0x2000>;
+ size = <0x1000>;
+ };
+ };
+ };
+
+The `sf-bootdev` driver can implement a way to read from the SPI flash, using
+the offset and size provided, and return that bootflow file back to the caller.
+When distro boot wants to read the kernel it calls disto_getfile() which must
+provide a way to read from the SPI flash. See `distro_boot()` at distro_boot_
+for more details.
+
+Of course this is all internal to U-Boot. All the distro sees is another way
+to boot.
+
+
+Configuration
+-------------
+
+Standard boot is enabled with `CONFIG_BOOTSTD`. Each bootmeth has its own CONFIG
+option also. For example, `CONFIG_BOOTMETH_DISTRO` enables support for distro
+boot from a disk.
+
+
+Available bootmeth drivers
+--------------------------
+
+Bootmeth drivers are provided for:
+
+ - distro boot from a disk (syslinux)
+ - distro boot from a network (PXE)
+ - EFI boot using bootefi
+ - EFI boot using boot manager
+
+
+Command interface
+-----------------
+
+Three commands are available:
+
+`bootdev`
+ Allows listing of available bootdevs, selecting a particular one and
+ getting information about it. See :doc:`../usage/cmd/bootdev`
+
+`bootflow`
+ Allows scanning one or more bootdevs for bootflows, listing available
+ bootflows, selecting one, obtaining information about it and booting it.
+ See :doc:`../usage/cmd/bootflow`
+
+`bootmeth`
+ Allow listing of available bootmethds and setting the order in which they
+ are tried. See :doc:`../usage/cmd/bootmeth`
+
+.. _BootflowStates:
+
+Bootflow states
+---------------
+
+Here is a list of states that a bootflow can be in:
+
+======= =======================================================================
+State Meaning
+======= =======================================================================
+base Starting-out state, indicates that no media/partition was found. For an
+ SD card socket it may indicate that the card is not inserted.
+media Media was found (e.g. SD card is inserted) but no partition information
+ was found. It might lack a partition table or have a read error.
+part Partition was found but a filesystem could not be read. This could be
+ because the partition does not hold a filesystem or the filesystem is
+ very corrupted.
+fs Filesystem was found but the file could not be read. It could be
+ missing or in the wrong subdirectory.
+file File was found and its size detected, but it could not be read. This
+ could indicate filesystem corruption.
+ready File was loaded and is ready for use. In this state the bootflow is
+ ready to be booted.
+======= =======================================================================
+
+
+Theory of operation
+-------------------
+
+This describes how standard boot progresses through to booting an operating
+system.
+
+To start. all the necessary devices must be bound, including bootstd, which
+provides the top-level `struct bootstd_priv` containing optional configuration
+information. The bootstd device is also holds the various lists used while
+scanning. This step is normally handled automatically by driver model, as
+described in `Automatic Devices`_.
+
+Bootdevs are also required, to provide access to the media to use. These are not
+useful by themselves: bootmeths are needed to provide the means of scanning
+those bootdevs. So, all up, we need a single bootstd device, one or more bootdev
+devices and one or more bootmeth devices.
+
+Once these are ready, typically a `bootflow scan` command is issued. This kicks
+of the iteration process, which involves looking through the bootdevs and their
+partitions one by one to find bootflows.
+
+Iteration is kicked off using `bootflow_scan_first()`, which calls
+`bootflow_scan_bootdev()`.
+
+The iterator is set up with `bootflow_iter_init()`. This simply creates an
+empty one with the given flags. Flags are used to control whether each
+iteration is displayed, whether to return iterations even if they did not result
+in a valid bootflow, whether to iterate through just a single bootdev, etc.
+
+Then the ordering of bootdevs is determined, by `bootdev_setup_iter_order()`. By
+default, the bootdevs are used in the order specified by the `boot_targets`
+environment variable (e.g. "mmc2 mmc0 usb"). If that is missing then their
+sequence order is used, as determined by the `/aliases` node, or failing that
+their order in the devicetree. For BOOTSTD_FULL, if there is a `bootdev-order`
+property in the bootstd node, then this is used as a final fallback. In any
+case, the iterator ends up with a `dev_order` array containing the bootdevs that
+are going to be used, with `num_devs` set to the number of bootdevs and
+`cur_dev` starting at 0.
+
+Next, the ordering of bootdevs is determined, by `bootmeth_setup_iter_order()`.
+By default the ordering is again by sequence number, i.e. the `/aliases` node,
+or failing that the order in the devicetree. But the `bootmeth order` command
+or `bootmeths` environment variable can be used to set up an ordering. If that
+has been done, the ordering is in `struct bootstd_priv`, so that ordering is
+simply copied into the iterator. Either way, the `method_order` array it set up,
+along with `num_methods`. Then `cur_method` is set to 0.
+
+At this point the iterator is ready to use, with the first bootdev and bootmeth
+selected. All the other fields are 0. This means that the current partition is
+0, which is taken to mean the whole device, since partition numbers start at 1.
+It also means that `max_part` is 0, i.e. the maximum partition number we know
+about is 0, meaning that, as far as we know, there is no partition table on this
+bootdev.
+
+With the iterator ready, `bootflow_scan_bootdev()` checks whether the current
+settings produce a valid bootflow. This is handled by `bootflow_check()`, which
+either returns 0 (if it got something) or an error if not (more on that later).
+If the `BOOTFLOWF_ALL` iterator flag is set, even errors are returned as
+incomplete bootflows, but normally an error results in moving onto the next
+iteration.
+
+The `bootflow_scan_next()` function handles moving onto the next iteration and
+checking it. In fact it sits in a loop doing that repeatedly until it finds
+something it wants to return.
+
+The actual 'moving on' part is implemented in `iter_incr()`. This is a very
+simple function. It increments the first counter. If that hits its maximum, it
+sets it to zero and increments the second counter. You can think of all the
+counters together as a number with three digits which increment in order, with
+the least-sigificant digit on the right, counting like this:
+
+ ======== ======= =======
+ bootdev part method
+ ======== ======= =======
+ 0 0 0
+ 0 0 1
+ 0 0 2
+ 0 1 0
+ 0 1 1
+ 0 1 1
+ 1 0 0
+ 1 0 1
+ ======== ======= =======
+
+The maximum value for `method` is `num_methods - 1` so when it exceeds that, it
+goes back to 0 and the next `part` is considered. The maximum value for that is
+`max_part`, which is initially zero for all bootdevs. If we find a partition
+table on that bootdev, `max_part` can be updated during the iteration to a
+higher value - see `bootdev_find_in_blk()` for that, described later. If that
+exceeds its maximum, then the next bootdev is used. In this way, iter_incr()
+works its way through all possibilities, moving forward one each time it is
+called.
+
+There is no expectation that iteration will actually finish. Quite often a
+valid bootflow is found early on. With `bootflow scan -b`, that causes the
+bootflow to be immediately booted. Assuming it is successful, the iteration never
+completes.
+
+Also note that the iterator hold the **current** combination being considered.
+So when `iter_incr()` is called, it increments to the next one and returns it,
+the new **current** combination.
+
+Note also the `err` field in `struct bootflow_iter`. This is normally 0 and has
+thus has no effect on `iter_inc()`. But if it is non-zero, signalling an error,
+it indicates to the iterator what it should do when called. It can force moving
+to the next partition, or bootdev, for example. The special values
+`BF_NO_MORE_PARTS` and `BF_NO_MORE_DEVICES` handle this. When `iter_incr` sees
+`BF_NO_MORE_PARTS` it knows that it should immediately move to the next bootdev.
+When it sees `BF_NO_MORE_DEVICES` it knows that there is nothing more it can do
+so it should immediately return. The caller of `iter_incr()` is responsible for
+updating the `err` field, based on the return value it sees.
+
+The above describes the iteration process at a high level. It is basically a
+very simple increment function with a checker called `bootflow_check()` that
+checks the result of each iteration generated, to determine whether it can
+produce a bootflow.
+
+So what happens inside of `bootflow_check()`? It simply calls the uclass
+method `bootdev_get_bootflow()` to ask the bootdev to return a bootflow. It
+passes the iterator to the bootdev method, so that function knows what we are
+talking about. At first, the bootflow is set up in the state `BOOTFLOWST_BASE`,
+with just the `method` and `dev` intiialised. But the bootdev may fill in more,
+e.g. updating the state, depending on what it finds.
+
+Based on what the bootdev responds with, `bootflow_check()` either
+returns a valid bootflow, or a partial one with an error. A partial bootflow
+is one that has some fields set up, but did not reach the `BOOTFLOWST_READY`
+state. As noted before, if the `BOOTFLOWF_ALL` iterator flag is set, then all
+bootflows are returned, even partial ones. This can help with debugging.
+
+So at this point you can see that total control over whether a bootflow can
+be generated from a particular iteration, or not, rests with the bootdev.
+Each one can adopt its own approach.
+
+Going down a level, what does the bootdev do in its `get_bootflow()` method?
+Let us consider the MMC bootdev. In that case the call to
+`bootdev_get_bootflow()` ends up in `mmc_get_bootflow()`. It locates the parent
+device of the bootdev, i.e. the `UCLASS_MMC` device itself, then finds the block
+device associated with it. It then calls the helper function
+`bootdev_find_in_blk()` to do all the work. This is common with just about any
+bootdev that is based on a media device.
+
+The `bootdev_find_in_blk()` helper is implemented in the bootdev uclass. It
+names the bootflow and copies the partition number in from the iterator. Then it
+calls the bootmeth device to check if it can support this device. This is
+important since some bootmeths only work with network devices, for example. If
+that check fails, it stops.
+
+Assuming the bootmeth is happy, or at least indicates that it is willing to try
+(by returning 0 from its `check()` method), the next step is to try the
+partition. If that works it tries to detect a file system. If that works then it
+calls the bootmeth device once more, this time to read the bootflow.
+
+Note: At present a filesystem is needed for the bootmeth to be called on block
+devices, simply because we don't have any examples where this is not the case.
+This feature can be added as needed.
+
+If we take the example of the `bootmeth_distro` driver, this call ends up at
+`distro_read_bootflow()`. It has the filesystem ready, so tries various
+filenames to try to find the `extlinux.conf` file, reading it if possible. If
+all goes well the bootflow ends up in the `BOOTFLOWST_READY` state.
+
+At this point, we fall back from the bootmeth driver, to
+`bootdev_find_in_blk()`, then back to `mmc_get_bootflow()`, then to
+`bootdev_get_bootflow()`, then to `bootflow_check()` and finally to its caller,
+either `bootflow_scan_bootdev()` or `bootflow_scan_next()`. In either case,
+the bootflow is returned as the result of this iteration, assuming it made it to
+the `BOOTFLOWST_READY` state.
+
+That is the basic operation of scanning for bootflows. The process of booting a
+bootflow is handled by the bootmeth driver for that bootflow. In the case of
+distro boot, this parses and processes the `extlinux.conf` file that was read.
+See `distro_boot()` for how that works. The processing may involve reading
+additional files, which is handled by the `read_file()` method, which is
+`distro_read_file()` in this case. All bootmethds should support reading files,
+since the bootflow is typically only the basic instructions and does not include
+the operating system itself, ramdisk, device tree, etc.
+
+The vast majority of the bootstd code is concerned with iterating through
+partitions on bootdevs and using bootmethds to find bootflows.
+
+How about bootdevs which are not block devices? They are handled by the same
+methods as above, but with a different implementation. For example, the bootmeth
+for PXE boot (over a network) uses `tftp` to read files rather than `fs_read()`.
+But other than that it is very similar.
+
+
+Tests
+-----
+
+Tests are located in `test/boot` and cover the core functionality as well as
+the commands. All tests use sandbox so can be run on a standard Linux computer
+and in U-Boot's CI.
+
+For testing, a DOS-formatted disk image is used with a single FAT partition on
+it. This is created in `setup_bootflow_image()`, with a canned one from the
+source tree used if it cannot be created (e.g. in CI).
+
+
+Bootflow internals
+------------------
+
+The bootstd device holds a linked list of scanned bootflows as well as the
+currently selected bootdev and bootflow (for use by commands). This is in
+`struct bootstd_priv`.
+
+Each bootdev device has its own `struct bootdev_uc_plat` which holds a
+list of scanned bootflows just for that device.
+
+The bootflow itself is documented in bootflow_h_. It includes various bits of
+information about the bootflow and a buffer to hold the file.
+
+
+Future
+------
+
+Apart from the to-do items below, different types of bootflow files may be
+implemented in future, e.g. Chromium OS support which is currently only
+available as a script in chromebook_coral.
+
+
+To do
+-----
+
+Some things that need to be done to completely replace the distro-boot scripts:
+
+- add bootdev drivers for dhcp, sata, scsi, ide, virtio
+- PXE boot for EFI
+- support for loading U-Boot scripts
+
+Other ideas:
+
+- `bootflow prep` to load everything preparing for boot, so that `bootflow boot`
+ can just do the boot.
+- automatically load kernel, FDT, etc. to suitable addresses so the board does
+ not need to specify things like `pxefile_addr_r`
+
+
+.. _disto_boodcmd: https://github.com/u-boot/u-boot/blob/master/include/config_distro_bootcmd.h
+.. _BootLoaderSpec: http://www.freedesktop.org/wiki/Specifications/BootLoaderSpec/
+.. _distro_boot: https://github.com/u-boot/u-boot/blob/master/boot/distro.c
+.. _bootflow_h: https://github.com/u-boot/u-boot/blob/master/include/bootflow.h
diff --git a/doc/develop/distro.rst b/doc/develop/distro.rst
index 0113a3d4ec..3ee3dac6a2 100644
--- a/doc/develop/distro.rst
+++ b/doc/develop/distro.rst
@@ -157,6 +157,9 @@ a line with "CONFIG_DISTRO_DEFAULTS=y". If you want to enable this
from Kconfig itself, for e.g. all boards using a specific SoC then
add a "imply DISTRO_DEFAULTS" to your SoC CONFIG option.
+
+TO BE UPDATED:
+
In your board configuration file, include the following::
#ifndef CONFIG_SPL_BUILD
diff --git a/doc/develop/index.rst b/doc/develop/index.rst
index 2e6d6c302a..d32ec49022 100644
--- a/doc/develop/index.rst
+++ b/doc/develop/index.rst
@@ -10,6 +10,7 @@ Implementation
:maxdepth: 1
bloblist
+ bootstd
ci_testing
commands
config_binding
diff --git a/doc/device-tree-bindings/bootdev.txt b/doc/device-tree-bindings/bootdev.txt
new file mode 100644
index 0000000000..4bb2345a0b
--- /dev/null
+++ b/doc/device-tree-bindings/bootdev.txt
@@ -0,0 +1,26 @@
+U-Boot boot device (bootdev)
+============================
+
+A bootdev provides a way to obtain a bootflow file from a device. It is a
+child of the media device (UCLASS_MMC, UCLASS_SPI_FLASH, etc.)
+
+The bootdev driver is provided by the media devices. The bindings for each
+are described in this file (to come).
+
+Required properties:
+
+compatible:
+ "u-boot,bootdev-eth" - Ethernet bootdev
+ "u-boot,bootdev-mmc" - MMC bootdev
+ "u-boot,bootdev-usb" - USB bootdev
+
+
+Example:
+
+ mmc1 {
+ compatible = "sandbox,mmc";
+
+ mmc-bootdev {
+ compatible = "u-boot,bootdev-eth";
+ };
+ };
diff --git a/doc/device-tree-bindings/bootmeth.txt b/doc/device-tree-bindings/bootmeth.txt
new file mode 100644
index 0000000000..de6396a7b3
--- /dev/null
+++ b/doc/device-tree-bindings/bootmeth.txt
@@ -0,0 +1,31 @@
+U-Boot standard boot methods (bootmeth)
+======================================
+
+This provides methods (called bootmeths) for locating bootflows on a boot
+device (bootdev). These are normally created as children of the bootstd device.
+
+Required properties:
+
+compatible:
+ "u-boot,distro-syslinux" - distro boot from a block device
+ "u-boot,distro-pxe" - distro boot from a network device
+ "u-boot,distro-efi" - EFI boot from an .efi file
+ "u-boot,efi-bootmgr" - EFI boot using boot manager (bootmgr)
+
+
+Example:
+
+ bootstd {
+ compatible = "u-boot,boot-std";
+
+ filename-prefixes = "/", "/boot/";
+ bootdev-order = "mmc2", "mmc1";
+
+ syslinux {
+ compatible = "u-boot,distro-syslinux";
+ };
+
+ efi {
+ compatible = "u-boot,distro-efi";
+ };
+ };
diff --git a/doc/device-tree-bindings/bootstd.txt b/doc/device-tree-bindings/bootstd.txt
new file mode 100644
index 0000000000..8706c5f499
--- /dev/null
+++ b/doc/device-tree-bindings/bootstd.txt
@@ -0,0 +1,36 @@
+U-Boot standard boot device (bootstd)
+=====================================
+
+This is the controlling device for U-Boot standard boot, providing a way to
+boot operating systems in a way that can be controlled by distros.
+
+Required properties:
+
+compatible: "u-boot,boot-std"
+
+Optional properties:
+
+filename-prefixes:
+ List of strings, each a directory to search for bootflow files
+
+bootdev-order:
+ List of bootdevs to check for bootflows, each a bootdev label (the media
+ uclass followed by the numeric sequence number of the media device)
+
+
+Example:
+
+ bootstd {
+ compatible = "u-boot,boot-std";
+
+ filename-prefixes = "/", "/boot/";
+ bootdev-order = "mmc2", "mmc1";
+
+ syslinux {
+ compatible = "u-boot,distro-syslinux";
+ };
+
+ efi {
+ compatible = "u-boot,distro-efi";
+ };
+ };
diff --git a/doc/usage/cmd/bootdev.rst b/doc/usage/cmd/bootdev.rst
new file mode 100644
index 0000000000..5e02e32c51
--- /dev/null
+++ b/doc/usage/cmd/bootdev.rst
@@ -0,0 +1,135 @@
+.. SPDX-License-Identifier: GPL-2.0+:
+
+bootdev command
+===============
+
+Synopis
+-------
+
+::
+
+ bootdev list [-p] - list all available bootdevs (-p to probe)\n"
+ bootdev select <bm> - select a bootdev by name\n"
+ bootdev info [-p] - show information about a bootdev";
+
+Description
+-----------
+
+The `bootdev` command is used to manage bootdevs. It can list available
+bootdevs, select one and obtain information about it.
+
+See :doc:`../../develop/bootstd` for more information about bootdevs in general.
+
+
+bootdev list
+~~~~~~~~~~~~
+
+This lists available bootdevs
+
+Scanning with `-p` causes the bootdevs to be probed. This happens automatically
+when they are used.
+
+The list looks something like this:
+
+=== ====== ====== ======== =========================
+Seq Probed Status Uclass Name
+=== ====== ====== ======== =========================
+ 0 [ + ] OK mmc mmc@7e202000.bootdev
+ 1 [ ] OK mmc sdhci@7e300000.bootdev
+ 2 [ ] OK ethernet smsc95xx_eth.bootdev
+=== ====== ====== ======== =========================
+
+
+The fields are as follows:
+
+Seq:
+ Sequence number in the scan, used to reference the bootflow later
+
+Probed:
+ Shows a plus (+) if the device is probed, empty if not.
+
+Status:
+ Shows the status of the device. Typically this is `OK` meaning that there is
+ no error. If you use -p and an error occurs when probing, then this shows
+ the error number. You can look up Linux error codes to find the meaning of
+ the number.
+
+Uclass:
+ Name of the media device's Uclass. This indicates the type of the parent
+ device (e.g. MMC, Ethernet).
+
+Name:
+ Name of the bootdev. This is generated from the media device appended
+ with `.bootdev`
+
+
+bootdev select
+~~~~~~~~~~~~~~~~~
+
+Use this to select a particular bootdev. You can select it by the sequence
+number or name, as shown in `bootdev list`.
+
+Once a bootdev is selected, you can use `bootdev info` to look at it or
+`bootflow scan` to scan it.
+
+If no bootdev name or number is provided, then any existing bootdev is
+unselected.
+
+
+bootdev info
+~~~~~~~~~~~~~~~
+
+This shows information on the current bootdev, with the format looking like
+this:
+
+========= =======================
+Name mmc@7e202000.bootdev
+Sequence 0
+Status Probed
+Uclass mmc
+Bootflows 1 (1 valid)
+========= =======================
+
+Most of the information is the same as `bootdev list` above. The new fields
+are:
+
+Device
+ Name of the bootdev
+
+Status
+ Shows `Probed` if the device is probed, `OK` if not. If `-p` is used and the
+ device fails to probe, an error code is shown.
+
+Bootflows
+ Indicates the number of bootflows attached to the bootdev. This is 0
+ unless you have used 'bootflow scan' on the bootflow, or on all bootflows.
+
+
+Example
+-------
+
+This example shows listing available bootdev and getting information about
+one of them::
+
+ U-Boot> bootdev list
+ Seq Probed Status Uclass Name
+ --- ------ ------ -------- ------------------
+ 0 [ + ] OK mmc mmc@7e202000.bootdev
+ 1 [ ] OK mmc sdhci@7e300000.bootdev
+ 2 [ ] OK ethernet smsc95xx_eth.bootdev
+ --- ------ ------ -------- ------------------
+ (3 devices)
+ U-Boot> bootdev sel 0
+ U-Boot> bootflow scan
+ U-Boot> bootdev info
+ Name: mmc@7e202000.bootdev
+ Sequence: 0
+ Status: Probed
+ Uclass: mmc
+ Bootflows: 1 (1 valid)
+
+
+Return value
+------------
+
+The return value $? is always 0 (true).
diff --git a/doc/usage/cmd/bootflow.rst b/doc/usage/cmd/bootflow.rst
new file mode 100644
index 0000000000..aa12dc2e3a
--- /dev/null
+++ b/doc/usage/cmd/bootflow.rst
@@ -0,0 +1,427 @@
+.. SPDX-License-Identifier: GPL-2.0+:
+
+bootflow command
+================
+
+Synopis
+-------
+
+::
+
+ bootflow scan [-abel] [bootdev]
+ bootflow list [-e]
+ bootflow select [<num|name>]
+ bootflow info [-d]
+ bootflow boot
+
+
+Description
+-----------
+
+The `bootflow` command is used to manage bootflows. It can scan bootdevs to
+locate bootflows, list them and boot them.
+
+See :doc:`../../develop/bootstd` for more information.
+
+
+bootflow scan
+~~~~~~~~~~~~~
+
+Scans for available bootflows, optionally booting the first valid one it finds.
+This operates in two modes:
+
+- If no bootdev is selected (see `bootdev select`) it scans bootflows one
+ by one, extracting all the bootdevs from each
+- If a bootdev is selected, it just scans that one bootflow
+
+Flags are:
+
+-a
+ Collect all bootflows, even those that cannot be loaded. Normally if a file
+ is not where it is expected, then the bootflow fails and so is dropped
+ during the scan. With this option you can see why each bootflow would be
+ dropped.
+
+-b
+ Boot each valid bootflow as it is scanned. Typically only the first bootflow
+ matters, since by then the system boots in the OS and U-Boot is no-longer
+ running. `bootflow scan -b` is a quick way to boot the first available OS.
+ A valid bootflow is one that made it all the way to the `loaded` state.
+
+-e
+ Used with -l to also show errors for each bootflow. The shows detailed error
+ information for each bootflow that failed to make it to the `loaded` state.
+
+-l
+ List bootflows while scanning. This is helpful when you want to see what
+ is happening during scanning. Use it with the `-b` flag to see which
+ bootdev and bootflows are being tried.
+
+The optional argument specifies a particular bootdev to scan. This can either be
+the name of a bootdev or its sequence number (both shown with `bootdev list`).
+Alternatively a convenience label can be used, like `mmc0`, which is the type of
+device and an optional sequence number. Specifically, the label is the uclass of
+the bootdev's parent followed by the sequence number of that parent. Sequence
+numbers are typically set by aliases, so if you have 'mmc0' in your devicetree
+alias section, then `mmc0` refers to the bootdev attached to that device.
+
+
+bootflow list
+~~~~~~~~~~~~~
+
+Lists the previously scanned bootflows. You must use `bootflow scan` before this
+to see anything.
+
+If you scanned with -a and have bootflows with errors, -e can be used to show
+those errors.
+
+The list looks something like this:
+
+=== ====== ====== ======== ==== =============================== ================
+Seq Method State Uclass Part Name Filename
+=== ====== ====== ======== ==== =============================== ================
+ 0 distro ready mmc 2 mmc\@7e202000.bootdev.part_2 /boot/extlinux/extlinux.conf
+ 1 pxe ready ethernet 0 smsc95xx_eth.bootdev.0 rpi.pxe/extlinux/extlinux.conf
+=== ====== ====== ======== ==== =============================== ================
+
+The fields are as follows:
+
+Seq:
+ Sequence number in the scan, used to reference the bootflow later
+
+Method:
+ The boot method (bootmeth) used to find the bootflow. Several methods are
+ included in U-Boot.
+
+State:
+ Current state of the bootflow, indicating how far the bootdev got in
+ obtaining a valid one. See :ref:`BootflowStates` for a list of states.
+
+Uclass:
+ Name of the media device's Uclass. This indicates the type of the parent
+ device (e.g. MMC, Ethernet).
+
+Part:
+ Partition number being accesseed, numbered from 1. Normally a device will
+ have a partition table with a small number of partitions. For devices
+ without partition tables (e.g. network) this field is 0.
+
+Name:
+ Name of the bootflow. This is generated from the bootdev appended with
+ the partition information
+
+Filename:
+ Name of the bootflow file. This indicates where the file is on the
+ filesystem or network device.
+
+
+bootflow select
+~~~~~~~~~~~~~~~
+
+Use this to select a particular bootflow. You can select it by the sequence
+number or name, as shown in `bootflow list`.
+
+Once a bootflow is selected, you can use `bootflow info` and `bootflow boot`.
+
+If no bootflow name or number is provided, then any existing bootflow is
+unselected.
+
+
+bootflow info
+~~~~~~~~~~~~~
+
+This shows information on the current bootflow, with the format looking like
+this:
+
+========= ===============================
+Name mmc\@7e202000.bootdev.part_2
+Device mmc\@7e202000.bootdev
+Block dev mmc\@7e202000.blk
+Type distro
+Method: syslinux
+State ready
+Partition 2
+Subdir (none)
+Filename /extlinux/extlinux.conf
+Buffer 3db7ad48
+Size 232 (562 bytes)
+Error 0
+========= ===============================
+
+Most of the information is the same as `bootflow list` above. The new fields
+are:
+
+Device
+ Name of the bootdev
+
+Block dev
+ Name of the block device, if any. Network devices don't have a block device.
+
+Subdir
+ Subdirectory used for retrieving files. For network bootdevs this is the
+ directory of the 'bootfile' parameter passed from DHCP. All file retrievals
+ when booting are relative to this.
+
+Buffer
+ Buffer containing the bootflow file. You can use the :doc:`md` to look at
+ it, or dump it with `bootflow info -d`.
+
+Size
+ Size of the bootflow file
+
+Error
+ Error number returned from scanning for the bootflow. This is 0 if the
+ bootflow is in the 'loaded' state, or a negative error value on error. You
+ can look up Linux error codes to find the meaning of the number.
+
+Use the `-d` flag to dump out the contents of the bootfile file.
+
+
+bootflow boot
+~~~~~~~~~~~~~
+
+This boots the current bootflow.
+
+
+Example
+-------
+
+Here is an example of scanning for bootflows, then listing them::
+
+ U-Boot> bootflow scan -l
+ Scanning for bootflows in all bootdevs
+ Seq Type State Uclass Part Name Filename
+ --- ----------- ------ -------- ---- ------------------------ ----------------
+ Scanning bootdev 'mmc@7e202000.bootdev':
+ 0 distro ready mmc 2 mmc@7e202000.bootdev.p /extlinux/extlinux.conf
+ Scanning bootdev 'sdhci@7e300000.bootdev':
+ Card did not respond to voltage select! : -110
+ Scanning bootdev 'smsc95xx_eth.bootdev':
+ Waiting for Ethernet connection... done.
+ BOOTP broadcast 1
+ DHCP client bound to address 192.168.4.30 (4 ms)
+ Using smsc95xx_eth device
+ TFTP from server 192.168.4.1; our IP address is 192.168.4.30
+ Filename 'rpi.pxe/'.
+ Load address: 0x200000
+ Loading: *
+ TFTP error: 'Is a directory' (0)
+ Starting again
+
+ missing environment variable: pxeuuid
+ Retrieving file: rpi.pxe/pxelinux.cfg/01-b8-27-eb-a6-61-e1
+ Waiting for Ethernet connection... done.
+ Using smsc95xx_eth device
+ TFTP from server 192.168.4.1; our IP address is 192.168.4.30
+ Filename 'rpi.pxe/pxelinux.cfg/01-b8-27-eb-a6-61-e1'.
+ Load address: 0x2500000
+ Loading: ################################################## 566 Bytes
+ 45.9 KiB/s
+ done
+ Bytes transferred = 566 (236 hex)
+ 1 distro ready ethernet 0 smsc95xx_eth.bootdev.0 rpi.pxe/extlinux/extlinux.conf
+ No more bootdevs
+ --- ----------- ------ -------- ---- ------------------------ ----------------
+ (2 bootflows, 2 valid)
+ U-Boot> bootflow l
+ Showing all bootflows
+ Seq Type State Uclass Part Name Filename
+ --- ----------- ------ -------- ---- ------------------------ ----------------
+ 0 distro ready mmc 2 mmc@7e202000.bootdev.p /extlinux/extlinux.conf
+ 1 pxe ready ethernet 0 smsc95xx_eth.bootdev.0 rpi.pxe/extlinux/extlinux.conf
+ --- ----------- ------ -------- ---- ------------------------ ----------------
+ (2 bootflows, 2 valid)
+
+
+The second one is then selected by name (we could instead use `bootflow sel 0`),
+displayed and booted::
+
+ U-Boot> bootflow info
+ No bootflow selected
+ U-Boot> bootflow sel mmc@7e202000.bootdev.part_2
+ U-Boot> bootflow info
+ Name: mmc@7e202000.bootdev.part_2
+ Device: mmc@7e202000.bootdev
+ Block dev: mmc@7e202000.blk
+ Sequence: 1
+ Method: distro
+ State: ready
+ Partition: 2
+ Subdir: (none)
+ Filename: extlinux/extlinux.conf
+ Buffer: 3db7ae88
+ Size: 232 (562 bytes)
+ Error: 0
+ U-Boot> bootflow boot
+ ** Booting bootflow 'smsc95xx_eth.bootdev.0'
+ Ignoring unknown command: ui
+ Ignoring malformed menu command: autoboot
+ Ignoring malformed menu command: hidden
+ Ignoring unknown command: totaltimeout
+ 1: Fedora-Workstation-armhfp-31-1.9 (5.3.7-301.fc31.armv7hl)
+ Retrieving file: rpi.pxe/initramfs-5.3.7-301.fc31.armv7hl.img
+ get 2700000 rpi.pxe/initramfs-5.3.7-301.fc31.armv7hl.img
+ Waiting for Ethernet connection... done.
+ Using smsc95xx_eth device
+ TFTP from server 192.168.4.1; our IP address is 192.168.4.30
+ Filename 'rpi.pxe/initramfs-5.3.7-301.fc31.armv7hl.img'.
+ Load address: 0x2700000
+ Loading: ###################################T ############### 57.7 MiB
+ 1.9 MiB/s
+ done
+ Bytes transferred = 60498594 (39b22a2 hex)
+ Retrieving file: rpi.pxe//vmlinuz-5.3.7-301.fc31.armv7hl
+ get 80000 rpi.pxe//vmlinuz-5.3.7-301.fc31.armv7hl
+ Waiting for Ethernet connection... done.
+ Using smsc95xx_eth device
+ TFTP from server 192.168.4.1; our IP address is 192.168.4.30
+ Filename 'rpi.pxe//vmlinuz-5.3.7-301.fc31.armv7hl'.
+ Load address: 0x80000
+ Loading: ################################################## 7.2 MiB
+ 2.3 MiB/s
+ done
+ Bytes transferred = 7508480 (729200 hex)
+ append: ro root=UUID=9732b35b-4cd5-458b-9b91-80f7047e0b8a rhgb quiet LANG=en_US.UTF-8 cma=192MB cma=256MB
+ Retrieving file: rpi.pxe//dtb-5.3.7-301.fc31.armv7hl/bcm2837-rpi-3-b.dtb
+ get 2600000 rpi.pxe//dtb-5.3.7-301.fc31.armv7hl/bcm2837-rpi-3-b.dtb
+ Waiting for Ethernet connection... done.
+ Using smsc95xx_eth device
+ TFTP from server 192.168.4.1; our IP address is 192.168.4.30
+ Filename 'rpi.pxe//dtb-5.3.7-301.fc31.armv7hl/bcm2837-rpi-3-b.dtb'.
+ Load address: 0x2600000
+ Loading: ################################################## 13.8 KiB
+ 764.6 KiB/s
+ done
+ Bytes transferred = 14102 (3716 hex)
+ Kernel image @ 0x080000 [ 0x000000 - 0x729200 ]
+ ## Flattened Device Tree blob at 02600000
+ Booting using the fdt blob at 0x2600000
+ Using Device Tree in place at 02600000, end 02606715
+
+ Starting kernel ...
+
+ [ OK ] Started Show Plymouth Boot Screen.
+ [ OK ] Started Forward Password R…s to Plymouth Directory Watch.
+ [ OK ] Reached target Local Encrypted Volumes.
+ [ OK ] Reached target Paths.
+ ....
+
+
+Here we scan for bootflows and boot the first one found::
+
+ U-Boot> bootflow scan -bl
+ Scanning for bootflows in all bootdevs
+ Seq Method State Uclass Part Name Filename
+ --- ----------- ------ -------- ---- ---------------------- ----------------
+ Scanning bootdev 'mmc@7e202000.bootdev':
+ 0 distro ready mmc 2 mmc@7e202000.bootdev.p /extlinux/extlinux.conf
+ ** Booting bootflow 'mmc@7e202000.bootdev.part_2'
+ Ignoring unknown command: ui
+ Ignoring malformed menu command: autoboot
+ Ignoring malformed menu command: hidden
+ Ignoring unknown command: totaltimeout
+ 1: Fedora-KDE-armhfp-31-1.9 (5.3.7-301.fc31.armv7hl)
+ Retrieving file: /initramfs-5.3.7-301.fc31.armv7hl.img
+ getfile 2700000 /initramfs-5.3.7-301.fc31.armv7hl.img
+ Retrieving file: /vmlinuz-5.3.7-301.fc31.armv7hl
+ getfile 80000 /vmlinuz-5.3.7-301.fc31.armv7hl
+ append: ro root=UUID=b8781f09-e2dd-4cb8-979b-7df5eeaaabea rhgb LANG=en_US.UTF-8 cma=192MB console=tty0 console=ttyS1,115200
+ Retrieving file: /dtb-5.3.7-301.fc31.armv7hl/bcm2837-rpi-3-b.dtb
+ getfile 2600000 /dtb-5.3.7-301.fc31.armv7hl/bcm2837-rpi-3-b.dtb
+ Kernel image @ 0x080000 [ 0x000000 - 0x729200 ]
+ ## Flattened Device Tree blob at 02600000
+ Booting using the fdt blob at 0x2600000
+ Using Device Tree in place at 02600000, end 02606715
+
+ Starting kernel ...
+
+ [ 0.000000] Booting Linux on physical CPU 0x0
+
+
+Here is am example using the -e flag to see all errors::
+
+ U-Boot> bootflow scan -a
+ Card did not respond to voltage select! : -110
+ Waiting for Ethernet connection... done.
+ BOOTP broadcast 1
+ DHCP client bound to address 192.168.4.30 (4 ms)
+ Using smsc95xx_eth device
+ TFTP from server 192.168.4.1; our IP address is 192.168.4.30
+ Filename 'rpi.pxe/'.
+ Load address: 0x200000
+ Loading: *
+ TFTP error: 'Is a directory' (0)
+ Starting again
+
+ missing environment variable: pxeuuid
+ Retrieving file: rpi.pxe/pxelinux.cfg/01-b8-27-eb-a6-61-e1
+ Waiting for Ethernet connection... done.
+ Using smsc95xx_eth device
+ TFTP from server 192.168.4.1; our IP address is 192.168.4.30
+ Filename 'rpi.pxe/pxelinux.cfg/01-b8-27-eb-a6-61-e1'.
+ Load address: 0x2500000
+ Loading: ################################################## 566 Bytes
+ 49.8 KiB/s
+ done
+ Bytes transferred = 566 (236 hex)
+ U-Boot> bootflow l -e
+ Showing all bootflows
+ Seq Type State Uclass Part Name Filename
+ --- ----------- ------ -------- ---- --------------------- ----------------
+ 0 distro fs mmc 1 mmc@7e202000.bootdev.p /extlinux/extlinux.conf
+ ** File not found, err=-2
+ 1 distro ready mmc 2 mmc@7e202000.bootdev.p /extlinux/extlinux.conf
+ 2 distro fs mmc 3 mmc@7e202000.bootdev.p /extlinux/extlinux.conf
+ ** File not found, err=-1
+ 3 distro media mmc 0 mmc@7e202000.bootdev.p <NULL>
+ ** No partition found, err=-2
+ 4 distro media mmc 0 mmc@7e202000.bootdev.p <NULL>
+ ** No partition found, err=-2
+ 5 distro media mmc 0 mmc@7e202000.bootdev.p <NULL>
+ ** No partition found, err=-2
+ 6 distro media mmc 0 mmc@7e202000.bootdev.p <NULL>
+ ** No partition found, err=-2
+ 7 distro media mmc 0 mmc@7e202000.bootdev.p <NULL>
+ ** No partition found, err=-2
+ 8 distro media mmc 0 mmc@7e202000.bootdev.p <NULL>
+ ** No partition found, err=-2
+ 9 distro media mmc 0 mmc@7e202000.bootdev.p <NULL>
+ ** No partition found, err=-2
+ a distro media mmc 0 mmc@7e202000.bootdev.p <NULL>
+ ** No partition found, err=-2
+ b distro media mmc 0 mmc@7e202000.bootdev.p <NULL>
+ ** No partition found, err=-2
+ c distro media mmc 0 mmc@7e202000.bootdev.p <NULL>
+ ** No partition found, err=-2
+ d distro media mmc 0 mmc@7e202000.bootdev.p <NULL>
+ ** No partition found, err=-2
+ e distro media mmc 0 mmc@7e202000.bootdev.p <NULL>
+ ** No partition found, err=-2
+ f distro media mmc 0 mmc@7e202000.bootdev.p <NULL>
+ ** No partition found, err=-2
+ 10 distro media mmc 0 mmc@7e202000.bootdev.p <NULL>
+ ** No partition found, err=-2
+ 11 distro media mmc 0 mmc@7e202000.bootdev.p <NULL>
+ ** No partition found, err=-2
+ 12 distro media mmc 0 mmc@7e202000.bootdev.p <NULL>
+ ** No partition found, err=-2
+ 13 distro media mmc 0 mmc@7e202000.bootdev.p <NULL>
+ ** No partition found, err=-2
+ 14 distro ready ethernet 0 smsc95xx_eth.bootdev.0 rpi.pxe/extlinux/extlinux.conf
+ --- ----------- ------ -------- ---- --------------------- ----------------
+ (21 bootflows, 2 valid)
+ U-Boot>
+
+
+Return value
+------------
+
+On success `bootflow boot` normally boots into the Operating System and does not
+return to U-Boot. If something about the U-Boot processing fails, then the
+return value $? is 1. If the boot succeeds but for some reason the Operating
+System returns, then $? is 0, indicating success.
+
+For other subcommands, the return value $? is always 0 (true).
+
+
+.. BootflowStates_:
diff --git a/doc/usage/cmd/bootmeth.rst b/doc/usage/cmd/bootmeth.rst
new file mode 100644
index 0000000000..9fc7ebf0ab
--- /dev/null
+++ b/doc/usage/cmd/bootmeth.rst
@@ -0,0 +1,108 @@
+.. SPDX-License-Identifier: GPL-2.0+:
+
+bootmeth command
+================
+
+Synopis
+-------
+
+::
+
+ bootmeth list [-a] - list selected bootmeths (-a for all)
+ bootmeth order "[<bm> ...]" - select the order of bootmeths\n"
+
+
+Description
+-----------
+
+The `bootmeth` command is used to manage bootmeths. It can list them and change
+the order in which they are used.
+
+See :doc:`../../develop/bootstd` for more information.
+
+
+.. _bootmeth_order:
+
+bootmeth order
+~~~~~~~~~~~~~~
+
+Selects which bootmeths to use and the order in which they are invoked. When
+scanning bootdevs, each bootmeth is tried in turn to see if it can find a valid
+bootflow. You can use this command to adjust the order or even to omit some
+boomeths.
+
+The argument is a quoted list of bootmeths to use, by name.
+
+
+bootmeth list
+~~~~~~~~~~~~~
+
+This lists the selected bootmeths, or all of them, if the `-a` flag is used.
+The format looks like this:
+
+===== === ================== =================================
+Order Seq Name Description
+===== === ================== =================================
+ 0 0 distro Syslinux boot from a block device
+ 1 1 efi EFI boot from an .efi file
+ 2 2 pxe PXE boot from a network device
+ 3 3 sandbox Sandbox boot for testing
+ 4 4 efi_mgr EFI bootmgr flow
+===== === ================== =================================
+
+The fields are as follows:
+
+Order:
+ The order in which these bootmeths are invoked for each bootdev. If this
+ shows as a hyphen, then the bootmeth is not in the current ordering.
+
+Seq:
+ The sequence number of the bootmeth, i.e. the normal ordering if none is set
+
+Name:
+ Name of the bootmeth
+
+Description:
+ A friendly description for the bootmeth
+
+
+Example
+-------
+
+This shows listing bootmeths. All are present and in the normal order::
+
+ => bootmeth list
+ Order Seq Name Description
+ ----- --- ------------------ ------------------
+ 0 0 distro Syslinux boot from a block device
+ 1 1 efi EFI boot from an .efi file
+ 2 2 pxe PXE boot from a network device
+ 3 3 sandbox Sandbox boot for testing
+ 4 4 efi_mgr EFI bootmgr flow
+ ----- --- ------------------ ------------------
+ (5 bootmeths)
+
+Now the order is changed, to include only two of them::
+
+ => bootmeth order "sandbox distro"
+ => bootmeth list
+ Order Seq Name Description
+ ----- --- ------------------ ------------------
+ 0 3 sandbox Sandbox boot for testing
+ 1 0 distro Syslinux boot from a block device
+ ----- --- ------------------ ------------------
+ (2 bootmeths)
+
+The -a flag shows all bootmeths so you can clearly see which ones are used and
+which are not::
+
+ => bootmeth list -a
+ Order Seq Name Description
+ ----- --- ------------------ ------------------
+ 1 0 distro Syslinux boot from a block device
+ - 1 efi EFI boot from an .efi file
+ - 2 pxe PXE boot from a network device
+ 0 3 sandbox Sandbox boot for testing
+ - 4 efi_mgr EFI bootmgr flow
+ ----- --- ------------------ ------------------
+ (5 bootmeths)
diff --git a/doc/usage/index.rst b/doc/usage/index.rst
index f343e4e05f..c03f4aef9e 100644
--- a/doc/usage/index.rst
+++ b/doc/usage/index.rst
@@ -23,9 +23,12 @@ Shell commands
cmd/addrmap
cmd/askenv
cmd/base
+ cmd/bootdev
cmd/bootefi
+ cmd/bootflow
cmd/booti
cmd/bootmenu
+ cmd/bootmeth
cmd/button
cmd/cbsysinfo
cmd/conitrace
diff --git a/drivers/block/blk-uclass.c b/drivers/block/blk-uclass.c
index 791e26c06e..21c5209bb6 100644
--- a/drivers/block/blk-uclass.c
+++ b/drivers/block/blk-uclass.c
@@ -509,6 +509,13 @@ int blk_get_from_parent(struct udevice *parent, struct udevice **devp)
return 0;
}
+const char *blk_get_devtype(struct udevice *dev)
+{
+ struct udevice *parent = dev_get_parent(dev);
+
+ return uclass_get_name(device_get_uclass_id(parent));
+};
+
int blk_find_max_devnum(enum if_type if_type)
{
struct udevice *dev;
diff --git a/drivers/core/uclass.c b/drivers/core/uclass.c
index 2578803b7a..08d9ed82de 100644
--- a/drivers/core/uclass.c
+++ b/drivers/core/uclass.c
@@ -180,14 +180,15 @@ void uclass_set_priv(struct uclass *uc, void *priv)
uc->priv_ = priv;
}
-enum uclass_id uclass_get_by_name_len(const char *name, int len)
+enum uclass_id uclass_get_by_namelen(const char *name, int len)
{
int i;
for (i = 0; i < UCLASS_COUNT; i++) {
struct uclass_driver *uc_drv = lists_uclass_lookup(i);
- if (uc_drv && !strncmp(uc_drv->name, name, len))
+ if (uc_drv && !strncmp(uc_drv->name, name, len) &&
+ strlen(uc_drv->name) == len)
return i;
}
@@ -196,7 +197,7 @@ enum uclass_id uclass_get_by_name_len(const char *name, int len)
enum uclass_id uclass_get_by_name(const char *name)
{
- return uclass_get_by_name_len(name, strlen(name));
+ return uclass_get_by_namelen(name, strlen(name));
}
int dev_get_uclass_index(struct udevice *dev, struct uclass **ucp)
@@ -273,8 +274,8 @@ int uclass_find_next_device(struct udevice **devp)
return 0;
}
-int uclass_find_device_by_name(enum uclass_id id, const char *name,
- struct udevice **devp)
+int uclass_find_device_by_namelen(enum uclass_id id, const char *name, int len,
+ struct udevice **devp)
{
struct uclass *uc;
struct udevice *dev;
@@ -288,7 +289,8 @@ int uclass_find_device_by_name(enum uclass_id id, const char *name,
return ret;
uclass_foreach_dev(dev, uc) {
- if (!strcmp(dev->name, name)) {
+ if (!strncmp(dev->name, name, len) &&
+ strlen(dev->name) == len) {
*devp = dev;
return 0;
}
@@ -297,6 +299,12 @@ int uclass_find_device_by_name(enum uclass_id id, const char *name,
return -ENODEV;
}
+int uclass_find_device_by_name(enum uclass_id id, const char *name,
+ struct udevice **devp)
+{
+ return uclass_find_device_by_namelen(id, name, strlen(name), devp);
+}
+
int uclass_find_next_free_seq(struct uclass *uc)
{
struct udevice *dev;
diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile
index 17ebc04203..9627509302 100644
--- a/drivers/mmc/Makefile
+++ b/drivers/mmc/Makefile
@@ -5,6 +5,11 @@
obj-y += mmc.o
obj-$(CONFIG_$(SPL_)DM_MMC) += mmc-uclass.o
+
+ifdef CONFIG_$(SPL_TPL_)DM_MMC
+obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += mmc_bootdev.o
+endif
+
obj-$(CONFIG_$(SPL_)MMC_WRITE) += mmc_write.o
obj-$(CONFIG_MMC_PWRSEQ) += mmc-pwrseq.o
obj-$(CONFIG_MMC_SDHCI_ADMA_HELPERS) += sdhci-adma.o
diff --git a/drivers/mmc/mmc-uclass.c b/drivers/mmc/mmc-uclass.c
index 57da788ad8..688bdc06d4 100644
--- a/drivers/mmc/mmc-uclass.c
+++ b/drivers/mmc/mmc-uclass.c
@@ -8,6 +8,7 @@
#define LOG_CATEGORY UCLASS_MMC
#include <common.h>
+#include <bootdev.h>
#include <log.h>
#include <mmc.h>
#include <dm.h>
@@ -315,6 +316,20 @@ int mmc_get_next_devnum(void)
return blk_find_max_devnum(IF_TYPE_MMC);
}
+int mmc_get_blk(struct udevice *dev, struct udevice **blkp)
+{
+ struct udevice *blk;
+ int ret;
+
+ device_find_first_child_by_uclass(dev, UCLASS_BLK, &blk);
+ ret = device_probe(blk);
+ if (ret)
+ return ret;
+ *blkp = blk;
+
+ return 0;
+}
+
struct blk_desc *mmc_get_blk_desc(struct mmc *mmc)
{
struct blk_desc *desc;
@@ -406,6 +421,10 @@ int mmc_bind(struct udevice *dev, struct mmc *mmc, const struct mmc_config *cfg)
mmc->cfg = cfg;
mmc->priv = dev;
+ ret = bootdev_setup_for_dev(dev, "mmc_bootdev");
+ if (ret)
+ return log_msg_ret("bootdev", ret);
+
/* the following chunk was from mmc_register() */
/* Setup dsr related values */
@@ -424,12 +443,16 @@ int mmc_bind(struct udevice *dev, struct mmc *mmc, const struct mmc_config *cfg)
int mmc_unbind(struct udevice *dev)
{
struct udevice *bdev;
+ int ret;
device_find_first_child_by_uclass(dev, UCLASS_BLK, &bdev);
if (bdev) {
device_remove(bdev, DM_REMOVE_NORMAL);
device_unbind(bdev);
}
+ ret = bootdev_unbind_dev(dev);
+ if (ret)
+ return log_msg_ret("bootdev", ret);
return 0;
}
diff --git a/drivers/mmc/mmc_bootdev.c b/drivers/mmc/mmc_bootdev.c
new file mode 100644
index 0000000000..b4f41fb3a6
--- /dev/null
+++ b/drivers/mmc/mmc_bootdev.c
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Bootdevice for MMC
+ *
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <common.h>
+#include <bootdev.h>
+#include <dm.h>
+#include <mmc.h>
+
+static int mmc_get_bootflow(struct udevice *dev, struct bootflow_iter *iter,
+ struct bootflow *bflow)
+{
+ struct udevice *mmc_dev = dev_get_parent(dev);
+ struct udevice *blk;
+ int ret;
+
+ ret = mmc_get_blk(mmc_dev, &blk);
+ /*
+ * 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;
+}
+
+static int mmc_bootdev_bind(struct udevice *dev)
+{
+ struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev);
+
+ ucp->prio = BOOTDEVP_0_INTERNAL_FAST;
+
+ return 0;
+}
+
+struct bootdev_ops mmc_bootdev_ops = {
+ .get_bootflow = mmc_get_bootflow,
+};
+
+static const struct udevice_id mmc_bootdev_ids[] = {
+ { .compatible = "u-boot,bootdev-mmc" },
+ { }
+};
+
+U_BOOT_DRIVER(mmc_bootdev) = {
+ .name = "mmc_bootdev",
+ .id = UCLASS_BOOTDEV,
+ .ops = &mmc_bootdev_ops,
+ .bind = mmc_bootdev_bind,
+ .of_match = mmc_bootdev_ids,
+};
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
index a4472da9f1..7785b3744e 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile
@@ -8,6 +8,10 @@ obj-y += usb-uclass.o
obj-$(CONFIG_SANDBOX) += usb-sandbox.o
endif
+ifdef CONFIG_$(SPL_TPL_)USB_STORAGE
+obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += usb_bootdev.o
+endif
+
# ohci
obj-$(CONFIG_USB_OHCI_NEW) += ohci-hcd.o
obj-$(CONFIG_USB_ATMEL) += ohci-at91.o
diff --git a/drivers/usb/host/usb_bootdev.c b/drivers/usb/host/usb_bootdev.c
new file mode 100644
index 0000000000..b85f699933
--- /dev/null
+++ b/drivers/usb/host/usb_bootdev.c
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Bootdevice for USB
+ *
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <common.h>
+#include <bootdev.h>
+#include <dm.h>
+#include <usb.h>
+
+static int usb_get_bootflow(struct udevice *dev, struct bootflow_iter *iter,
+ struct bootflow *bflow)
+{
+ struct udevice *blk;
+ int ret;
+
+ ret = bootdev_get_sibling_blk(dev, &blk);
+ /*
+ * 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;
+}
+
+static int usb_bootdev_bind(struct udevice *dev)
+{
+ struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev);
+
+ ucp->prio = BOOTDEVP_3_SCAN_SLOW;
+
+ return 0;
+}
+
+struct bootdev_ops usb_bootdev_ops = {
+ .get_bootflow = usb_get_bootflow,
+};
+
+static const struct udevice_id usb_bootdev_ids[] = {
+ { .compatible = "u-boot,bootdev-usb" },
+ { }
+};
+
+U_BOOT_DRIVER(usb_bootdev) = {
+ .name = "usb_bootdev",
+ .id = UCLASS_BOOTDEV,
+ .ops = &usb_bootdev_ops,
+ .bind = usb_bootdev_bind,
+ .of_match = usb_bootdev_ids,
+};
diff --git a/fs/Kconfig b/fs/Kconfig
index cda9f66cc9..aa13d4faa7 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -16,6 +16,8 @@ source "fs/fat/Kconfig"
source "fs/jffs2/Kconfig"
+source "fs/sandbox/Kconfig"
+
source "fs/ubifs/Kconfig"
source "fs/cramfs/Kconfig"
diff --git a/fs/fs.c b/fs/fs.c
index b812597ced..6de1a3eb6d 100644
--- a/fs/fs.c
+++ b/fs/fs.c
@@ -36,6 +36,11 @@ static int fs_dev_part;
static struct disk_partition fs_partition;
static int fs_type = FS_TYPE_ANY;
+void fs_set_type(int type)
+{
+ fs_type = type;
+}
+
static inline int fs_probe_unsupported(struct blk_desc *fs_dev_desc,
struct disk_partition *fs_partition)
{
diff --git a/fs/sandbox/Kconfig b/fs/sandbox/Kconfig
new file mode 100644
index 0000000000..b2af848242
--- /dev/null
+++ b/fs/sandbox/Kconfig
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0+
+#
diff --git a/fs/sandbox/Makefile b/fs/sandbox/Makefile
index 06090519bf..880d59dd69 100644
--- a/fs/sandbox/Makefile
+++ b/fs/sandbox/Makefile
@@ -9,3 +9,4 @@
# Pavel Bartusek, Sysgo Real-Time Solutions AG, pba@sysgo.de
obj-y := sandboxfs.o
+obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += host_bootdev.o
diff --git a/fs/sandbox/host_bootdev.c b/fs/sandbox/host_bootdev.c
new file mode 100644
index 0000000000..0d12ee4ef7
--- /dev/null
+++ b/fs/sandbox/host_bootdev.c
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Bootdevice for MMC
+ *
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <common.h>
+#include <bootdev.h>
+#include <bootflow.h>
+#include <bootmeth.h>
+#include <dm.h>
+#include <fs.h>
+
+static int host_get_bootflow(struct udevice *dev, struct bootflow_iter *iter,
+ struct bootflow *bflow)
+{
+ int ret;
+
+ if (iter->part)
+ return log_msg_ret("max", -ESHUTDOWN);
+
+ bflow->name = strdup(dev->name);
+ if (!bflow->name)
+ return log_msg_ret("name", -ENOMEM);
+
+ ret = bootmeth_check(bflow->method, iter);
+ if (ret)
+ return log_msg_ret("check", ret);
+
+ bflow->state = BOOTFLOWST_MEDIA;
+ bflow->fs_type = FS_TYPE_SANDBOX;
+
+ ret = bootmeth_read_bootflow(bflow->method, bflow);
+ if (ret)
+ return log_msg_ret("method", ret);
+
+ return 0;
+}
+
+struct bootdev_ops host_bootdev_ops = {
+ .get_bootflow = host_get_bootflow,
+};
+
+static const struct udevice_id host_bootdev_ids[] = {
+ { .compatible = "sandbox,bootdev-host" },
+ { }
+};
+
+U_BOOT_DRIVER(host_bootdev) = {
+ .name = "host_bootdev",
+ .id = UCLASS_BOOTDEV,
+ .ops = &host_bootdev_ops,
+ .of_match = host_bootdev_ids,
+};
diff --git a/include/blk.h b/include/blk.h
index dbe9ae219d..9503369db8 100644
--- a/include/blk.h
+++ b/include/blk.h
@@ -434,6 +434,14 @@ int blk_select_hwpart(struct udevice *dev, int hwpart);
int blk_get_from_parent(struct udevice *parent, struct udevice **devp);
/**
+ * blk_get_devtype() - Get the device type of a block device
+ *
+ * @dev: Block device to check
+ * Return: device tree, i.e. the uclass name of its parent, e.g. "mmc"
+ */
+const char *blk_get_devtype(struct udevice *dev);
+
+/**
* blk_get_by_device() - Get the block device descriptor for the given device
* @dev: Instance of a storage device
*
diff --git a/include/bootdev.h b/include/bootdev.h
new file mode 100644
index 0000000000..9fc219839f
--- /dev/null
+++ b/include/bootdev.h
@@ -0,0 +1,275 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#ifndef __bootdev_h
+#define __bootdev_h
+
+#include <linux/list.h>
+
+struct bootflow;
+struct bootflow_iter;
+struct udevice;
+
+/**
+ * enum bootdev_prio_t - priority of each bootdev
+ *
+ * These values are associated with each bootdev and set up by the driver.
+ *
+ * Smallest value is the highest priority. By default, bootdevs are scanned from
+ * highest to lowest priority
+ */
+enum bootdev_prio_t {
+ BOOTDEVP_0_INTERNAL_FAST = 10,
+ BOOTDEVP_1_INTERNAL_SLOW = 20,
+ BOOTDEVP_2_SCAN_FAST = 30,
+ BOOTDEVP_3_SCAN_SLOW = 40,
+ BOOTDEVP_4_NET_BASE = 50,
+ BOOTDEVP_5_NET_FALLBACK = 60,
+ BOOTDEVP_6_SYSTEM = 70,
+
+ BOOTDEVP_COUNT,
+};
+
+/**
+ * struct bootdev_uc_plat - uclass information about a bootdev
+ *
+ * This is attached to each device in the bootdev uclass and accessible via
+ * dev_get_uclass_plat(dev)
+ *
+ * @bootflows: List of available bootflows for this bootdev
+ * @piro: Priority of this bootdev
+ */
+struct bootdev_uc_plat {
+ struct list_head bootflow_head;
+ enum bootdev_prio_t prio;
+};
+
+/** struct bootdev_ops - Operations for the bootdev uclass */
+struct bootdev_ops {
+ /**
+ * get_bootflow() - get a bootflow
+ *
+ * @dev: Bootflow device to check
+ * @iter: Provides current dev, part, method to get. Should update
+ * max_part if there is a partition table. Should update state,
+ * subdir, fname, buf, size according to progress
+ * @bflow: Updated bootflow if found
+ * Return: 0 if OK, -ESHUTDOWN if there are no more bootflows on this
+ * device, -ENOSYS if this device doesn't support bootflows,
+ * other -ve value on other error
+ */
+ int (*get_bootflow)(struct udevice *dev, struct bootflow_iter *iter,
+ struct bootflow *bflow);
+};
+
+#define bootdev_get_ops(dev) ((struct bootdev_ops *)(dev)->driver->ops)
+
+/**
+ * bootdev_get_bootflow() - get a bootflow
+ *
+ * @dev: Bootflow device to check
+ * @iter: Provides current part, method to get
+ * @bflow: Returns bootflow if found
+ * Return: 0 if OK, -ESHUTDOWN if there are no more bootflows on this device,
+ * -ENOSYS if this device doesn't support bootflows, other -ve value on
+ * other error
+ */
+int bootdev_get_bootflow(struct udevice *dev, struct bootflow_iter *iter,
+ struct bootflow *bflow);
+
+/**
+ * bootdev_bind() - Bind a new named bootdev device
+ *
+ * @parent: Parent of the new device
+ * @drv_name: Driver name to use for the bootdev device
+ * @name: Name for the device (parent name is prepended)
+ * @devp: the new device (which has not been probed)
+ */
+int bootdev_bind(struct udevice *parent, const char *drv_name, const char *name,
+ struct udevice **devp);
+
+/**
+ * bootdev_find_in_blk() - Find a bootdev in a block device
+ *
+ * @dev: Bootflow device associated with this block device
+ * @blk: Block device to search
+ * @iter: Provides current dev, part, method to get. Should update
+ * max_part if there is a partition table
+ * @bflow: On entry, provides information about the partition and device to
+ * check. On exit, returns bootflow if found
+ * Return: 0 if found, -ESHUTDOWN if no more bootflows, other -ve on error
+ */
+int bootdev_find_in_blk(struct udevice *dev, struct udevice *blk,
+ struct bootflow_iter *iter, struct bootflow *bflow);
+
+/**
+ * bootdev_list() - List all available bootdevs
+ *
+ * @probe: true to probe devices, false to leave them as is
+ */
+void bootdev_list(bool probe);
+
+/**
+ * bootdev_clear_bootflows() - Clear bootflows from a bootdev
+ *
+ * Each bootdev maintains a list of discovered bootflows. This provides a
+ * way to clear it. These bootflows are removed from the global list too.
+ *
+ * @dev: bootdev device to update
+ */
+void bootdev_clear_bootflows(struct udevice *dev);
+
+/**
+ * bootdev_add_bootflow() - Add a bootflow to the bootdev's list
+ *
+ * All fields in @bflow must be set up. Note that @bflow->dev is used to add the
+ * bootflow to that device.
+ *
+ * @dev: Bootdevice device to add to
+ * @bflow: Bootflow to add. Note that fields within bflow must be allocated
+ * since this function takes over ownership of these. This functions makes
+ * a copy of @bflow itself (without allocating its fields again), so the
+ * caller must dispose of the memory used by the @bflow pointer itself
+ * Return: 0 if OK, -ENOMEM if out of memory
+ */
+int bootdev_add_bootflow(struct bootflow *bflow);
+
+/**
+ * bootdev_first_bootflow() - Get the first bootflow from a bootdev
+ *
+ * Returns the first bootflow attached to a bootdev
+ *
+ * @dev: bootdev device
+ * @bflowp: Returns a pointer to the bootflow
+ * Return: 0 if found, -ENOENT if there are no bootflows
+ */
+int bootdev_first_bootflow(struct udevice *dev, struct bootflow **bflowp);
+
+/**
+ * bootdev_next_bootflow() - Get the next bootflow from a bootdev
+ *
+ * Returns the next bootflow attached to a bootdev
+ *
+ * @bflowp: On entry, the last bootflow returned , e.g. from
+ * bootdev_first_bootflow()
+ * Return: 0 if found, -ENOENT if there are no more bootflows
+ */
+int bootdev_next_bootflow(struct bootflow **bflowp);
+
+/**
+ * bootdev_find_by_label() - Look up a bootdev by label
+ *
+ * Each bootdev has a label which contains the media-uclass name and a number,
+ * e.g. 'mmc2'. This looks up the label and returns the associated bootdev
+ *
+ * The lookup is performed based on the media device's sequence number. So for
+ * 'mmc2' this looks for a device in UCLASS_MMC with a dev_seq() of 2.
+ *
+ * @label: Label to look up (e.g. "mmc1" or "mmc0")
+ * @devp: Returns the bootdev device found, or NULL if none (note it does not
+ * return the media device, but its bootdev child)
+ * Return: 0 if OK, -EINVAL if the uclass is not supported by this board,
+ * -ENOENT if there is no device with that number
+ */
+int bootdev_find_by_label(const char *label, struct udevice **devp);
+
+/**
+ * bootdev_find_by_any() - Find a bootdev by name, label or sequence
+ *
+ * @name: name (e.g. "mmc2.bootdev"), label ("mmc2"), or sequence ("2") to find
+ * @devp: returns the device found, on success
+ * Return: 0 if OK, -ve on error
+ */
+int bootdev_find_by_any(const char *name, struct udevice **devp);
+
+/**
+ * bootdev_setup_iter_order() - Set up the ordering of bootdevs to scan
+ *
+ * This sets up the ordering information in @iter, based on the priority of each
+ * bootdev and the bootdev-order property in the bootstd node
+ *
+ * If a single device is requested, no ordering is needed
+ *
+ * @iter: Iterator to update with the order
+ * @devp: On entry, *devp is NULL to scan all, otherwise this is the (single)
+ * device to scan. Returns the first device to use, which is the passed-in
+ * @devp if it was non-NULL
+ * Return: 0 if OK, -ENOENT if no bootdevs, -ENOMEM if out of memory, other -ve
+ * on other error
+ */
+int bootdev_setup_iter_order(struct bootflow_iter *iter, struct udevice **devp);
+
+#if CONFIG_IS_ENABLED(BOOTSTD)
+/**
+ * bootdev_setup_for_dev() - Bind a new bootdev device
+ *
+ * Creates a bootdev device as a child of @parent. This should be called from
+ * the driver's bind() method or its uclass' post_bind() method.
+ *
+ * If a child bootdev already exists, this function does nothing
+ *
+ * @parent: Parent device (e.g. MMC or Ethernet)
+ * @drv_name: Name of bootdev driver to bind
+ * Return: 0 if OK, -ve on error
+ */
+int bootdev_setup_for_dev(struct udevice *parent, const char *drv_name);
+
+/**
+ * bootdev_setup_for_blk() - Bind a new bootdev device for a blk device
+ *
+ * Creates a bootdev device as a sibling of @blk. This should be called from
+ * the driver's bind() method or its uclass' post_bind() method, at the same
+ * time as the bould device is bound
+ *
+ * If a device of the same name already exists, this function does nothing
+ *
+ * @parent: Parent device (e.g. MMC or Ethernet)
+ * @drv_name: Name of bootdev driver to bind
+ * Return: 0 if OK, -ve on error
+ */
+int bootdev_setup_sibling_blk(struct udevice *blk, const char *drv_name);
+
+/**
+ * bootdev_get_sibling_blk() - Locate the block device for a bootdev
+ *
+ * @dev: bootdev to check
+ * @blkp: returns associated block device
+ * Return: 0 if OK, -EINVAL if @dev is not a bootdev device, other -ve on other
+ * error
+ */
+int bootdev_get_sibling_blk(struct udevice *dev, struct udevice **blkp);
+
+/**
+ * bootdev_unbind_dev() - Unbind a bootdev device
+ *
+ * Remove and unbind a bootdev device which is a child of @parent. This should
+ * be called from the driver's unbind() method or its uclass' post_bind()
+ * method.
+ *
+ * @parent: Parent device (e.g. MMC or Ethernet)
+ * Return: 0 if OK, -ve on error
+ */
+int bootdev_unbind_dev(struct udevice *parent);
+#else
+static inline int bootdev_setup_for_dev(struct udevice *parent,
+ const char *drv_name)
+{
+ return 0;
+}
+
+static inline int bootdev_setup_sibling_blk(struct udevice *blk,
+ const char *drv_name)
+{
+ return 0;
+}
+
+static inline int bootdev_unbind_dev(struct udevice *parent)
+{
+ return 0;
+}
+#endif
+
+#endif
diff --git a/include/bootflow.h b/include/bootflow.h
new file mode 100644
index 0000000000..c30ba042a4
--- /dev/null
+++ b/include/bootflow.h
@@ -0,0 +1,310 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#ifndef __bootflow_h
+#define __bootflow_h
+
+#include <linux/list.h>
+
+/**
+ * enum bootflow_state_t - states that a particular bootflow can be in
+ *
+ * Only bootflows in state BOOTFLOWST_READY can be used to boot.
+ *
+ * See bootflow_state[] for the names for each of these
+ */
+enum bootflow_state_t {
+ BOOTFLOWST_BASE, /**< Nothing known yet */
+ BOOTFLOWST_MEDIA, /**< Media exists */
+ BOOTFLOWST_PART, /**< Partition exists */
+ BOOTFLOWST_FS, /**< Filesystem exists */
+ BOOTFLOWST_FILE, /**< Bootflow file exists */
+ BOOTFLOWST_READY, /**< Bootflow file loaded */
+
+ BOOTFLOWST_COUNT
+};
+
+/**
+ * struct bootflow - information about a bootflow
+ *
+ * This is connected into two separate linked lists:
+ *
+ * bm_sibling - links all bootflows in the same bootdev
+ * glob_sibling - links all bootflows in all bootdevs
+ *
+ * @bm_node: Points to siblings in the same bootdev
+ * @glob_node: Points to siblings in the global list (all bootdev)
+ * @dev: Bootdevice device which produced this bootflow
+ * @blk: Block device which contains this bootflow, NULL if this is a network
+ * device
+ * @part: Partition number (0 for whole device)
+ * @fs_type: Filesystem type (FS_TYPE...) if this is fixed by the media, else 0.
+ * For example, the sandbox host-filesystem bootdev sets this to
+ * FS_TYPE_SANDBOX
+ * @method: Bootmethod device used to perform the boot and read files
+ * @name: Name of bootflow (allocated)
+ * @state: Current state (enum bootflow_state_t)
+ * @subdir: Subdirectory to fetch files from (with trailing /), or NULL if none
+ * @fname: Filename of bootflow file (allocated)
+ * @buf: Bootflow file contents (allocated)
+ * @size: Size of bootflow file in bytes
+ * @err: Error number received (0 if OK)
+ */
+struct bootflow {
+ struct list_head bm_node;
+ struct list_head glob_node;
+ struct udevice *dev;
+ struct udevice *blk;
+ int part;
+ int fs_type;
+ struct udevice *method;
+ char *name;
+ enum bootflow_state_t state;
+ char *subdir;
+ char *fname;
+ char *buf;
+ int size;
+ int err;
+};
+
+/**
+ * enum bootflow_flags_t - flags for the bootflow iterator
+ *
+ * @BOOTFLOWF_FIXED: Only used fixed/internal media
+ * @BOOTFLOWF_SHOW: Show each bootdev before scanning it
+ * @BOOTFLOWF_ALL: Return bootflows with errors as well
+ * @BOOTFLOWF_SINGLE_DEV: Just scan one bootmeth
+ */
+enum bootflow_flags_t {
+ BOOTFLOWF_FIXED = 1 << 0,
+ BOOTFLOWF_SHOW = 1 << 1,
+ BOOTFLOWF_ALL = 1 << 2,
+ BOOTFLOWF_SINGLE_DEV = 1 << 3,
+};
+
+/**
+ * struct bootflow_iter - state for iterating through bootflows
+ *
+ * This starts at with the first bootdev/partition/bootmeth and can be used to
+ * iterate through all of them.
+ *
+ * Iteration starts with the bootdev. The first partition (0, i.e. whole device)
+ * is scanned first. For partition 0, it iterates through all the available
+ * bootmeths to see which one(s) can provide a bootflow. Then it moves to
+ * parition 1 (if there is one) and the process continues. Once all partitions
+ * are examined, it moves to the next bootdev.
+ *
+ * Initially @max_part is 0, meaning that only the whole device (@part=0) can be
+ * used. During scanning, if a partition table is found, then @max_part is
+ * updated to a larger value, no less than the number of available partitions.
+ * This ensures that iteration works through all partitions on the bootdev.
+ *
+ * @flags: Flags to use (see enum bootflow_flags_t)
+ * @dev: Current bootdev
+ * @part: Current partition number (0 for whole device)
+ * @method: Current bootmeth
+ * @max_part: Maximum hardware partition number in @dev, 0 if there is no
+ * partition table
+ * @err: Error obtained from checking the last iteration. This is used to skip
+ * forward (e.g. to skip the current partition because it is not valid)
+ * -ESHUTDOWN: try next bootdev
+ * @num_devs: Number of bootdevs in @dev_order
+ * @cur_dev: Current bootdev number, an index into @dev_order[]
+ * @dev_order: List of bootdevs to scan, in order of priority. The scan starts
+ * with the first one on the list
+ * @num_methods: Number of bootmeth devices in @method_order
+ * @cur_method: Current method number, an index into @method_order
+ * @method_order: List of bootmeth devices to use, in order
+ */
+struct bootflow_iter {
+ int flags;
+ struct udevice *dev;
+ int part;
+ struct udevice *method;
+ int max_part;
+ int err;
+ int num_devs;
+ int cur_dev;
+ struct udevice **dev_order;
+ int num_methods;
+ int cur_method;
+ struct udevice **method_order;
+};
+
+/**
+ * bootflow_iter_init() - Reset a bootflow iterator
+ *
+ * This sets everything to the starting point, ready for use.
+ *
+ * @iter: Place to store private info (inited by this call)
+ * @flags: Flags to use (see enum bootflow_flags_t)
+ */
+void bootflow_iter_init(struct bootflow_iter *iter, int flags);
+
+/**
+ * bootflow_iter_uninit() - Free memory used by an interator
+ *
+ * @iter: Iterator to free
+ */
+void bootflow_iter_uninit(struct bootflow_iter *iter);
+
+/**
+ * bootflow_iter_drop_bootmeth() - Remove a bootmeth from an iterator
+ *
+ * Update the iterator so that the bootmeth will not be used again while this
+ * iterator is in use
+ *
+ * @iter: Iterator to update
+ * @bmeth: Boot method to remove
+ */
+int bootflow_iter_drop_bootmeth(struct bootflow_iter *iter,
+ const struct udevice *bmeth);
+
+/**
+ * bootflow_scan_bootdev() - find the first bootflow in a bootdev
+ *
+ * If @flags includes BOOTFLOWF_ALL then bootflows with errors are returned too
+ *
+ * @dev: Boot device to scan, NULL to work through all of them until it
+ * finds one that * can supply a bootflow
+ * @iter: Place to store private info (inited by this call)
+ * @flags: Flags for bootdev (enum bootflow_flags_t)
+ * @bflow: Place to put the bootflow if found
+ * Return: 0 if found, -ENODEV if no device, other -ve on other error
+ * (iteration can continue)
+ */
+int bootflow_scan_bootdev(struct udevice *dev, struct bootflow_iter *iter,
+ int flags, struct bootflow *bflow);
+
+/**
+ * bootflow_scan_first() - find the first bootflow
+ *
+ * This works through the available bootdev devices until it finds one that
+ * can supply a bootflow. It then returns that
+ *
+ * If @flags includes BOOTFLOWF_ALL then bootflows with errors are returned too
+ *
+ * @iter: Place to store private info (inited by this call), with
+ * @flags: Flags for bootdev (enum bootflow_flags_t)
+ * @bflow: Place to put the bootflow if found
+ * Return: 0 if found, -ENODEV if no device, other -ve on other error (iteration
+ * can continue)
+ */
+int bootflow_scan_first(struct bootflow_iter *iter, int flags,
+ struct bootflow *bflow);
+
+/**
+ * bootflow_scan_next() - find the next bootflow
+ *
+ * This works through the available bootdev devices until it finds one that
+ * can supply a bootflow. It then returns that bootflow
+ *
+ * @iter: Private info (as set up by bootflow_scan_first())
+ * @bflow: Place to put the bootflow if found
+ * Return: 0 if found, -ENODEV if no device, -ESHUTDOWN if no more bootflows,
+ * other -ve on other error (iteration can continue)
+ */
+int bootflow_scan_next(struct bootflow_iter *iter, struct bootflow *bflow);
+
+/**
+ * bootflow_first_glob() - Get the first bootflow from the global list
+ *
+ * Returns the first bootflow in the global list, no matter what bootflow it is
+ * attached to
+ *
+ * @bflowp: Returns a pointer to the bootflow
+ * Return: 0 if found, -ENOENT if there are no bootflows
+ */
+int bootflow_first_glob(struct bootflow **bflowp);
+
+/**
+ * bootflow_next_glob() - Get the next bootflow from the global list
+ *
+ * Returns the next bootflow in the global list, no matter what bootflow it is
+ * attached to
+ *
+ * @bflowp: On entry, the last bootflow returned , e.g. from
+ * bootflow_first_glob()
+ * Return: 0 if found, -ENOENT if there are no more bootflows
+ */
+int bootflow_next_glob(struct bootflow **bflowp);
+
+/**
+ * bootflow_free() - Free memory used by a bootflow
+ *
+ * This frees fields within @bflow, but not the @bflow pointer itself
+ */
+void bootflow_free(struct bootflow *bflow);
+
+/**
+ * bootflow_boot() - boot a bootflow
+ *
+ * @bflow: Bootflow to boot
+ * Return: -EPROTO if bootflow has not been loaded, -ENOSYS if the bootflow
+ * type is not supported, -EFAULT if the boot returned without an error
+ * when we are expecting it to boot, -ENOTSUPP if trying method resulted in
+ * finding out that is not actually supported for this boot and should not
+ * be tried again unless something changes
+ */
+int bootflow_boot(struct bootflow *bflow);
+
+/**
+ * bootflow_run_boot() - Try to boot a bootflow
+ *
+ * @iter: Current iteration (or NULL if none). Used to disable a bootmeth if the
+ * boot returns -ENOTSUPP
+ * @bflow: Bootflow to boot
+ * Return: result of trying to boot
+ */
+int bootflow_run_boot(struct bootflow_iter *iter, struct bootflow *bflow);
+
+/**
+ * bootflow_state_get_name() - Get the name of a bootflow state
+ *
+ * @state: State to check
+ * Return: name, or "?" if invalid
+ */
+const char *bootflow_state_get_name(enum bootflow_state_t state);
+
+/**
+ * bootflow_remove() - Remove a bootflow and free its memory
+ *
+ * This updates the linked lists containing the bootflow then frees it.
+ *
+ * @bflow: Bootflow to remove
+ */
+void bootflow_remove(struct bootflow *bflow);
+
+/**
+ * bootflow_iter_uses_blk_dev() - Check that a bootflow uses a block device
+ *
+ * This checks the bootdev in the bootflow to make sure it uses a block device
+ *
+ * Return: 0 if OK, -ENOTSUPP if some other device is used (e.g. ethernet)
+ */
+int bootflow_iter_uses_blk_dev(const struct bootflow_iter *iter);
+
+/**
+ * bootflow_iter_uses_network() - Check that a bootflow uses a network device
+ *
+ * This checks the bootdev in the bootflow to make sure it uses a network
+ * device
+ *
+ * Return: 0 if OK, -ENOTSUPP if some other device is used (e.g. MMC)
+ */
+int bootflow_iter_uses_network(const struct bootflow_iter *iter);
+
+/**
+ * bootflow_iter_uses_system() - Check that a bootflow uses the bootstd device
+ *
+ * This checks the bootdev in the bootflow to make sure it uses the bootstd
+ * device
+ *
+ * Return: 0 if OK, -ENOTSUPP if some other device is used (e.g. MMC)
+ */
+int bootflow_iter_uses_system(const struct bootflow_iter *iter);
+
+#endif
diff --git a/include/bootmeth.h b/include/bootmeth.h
new file mode 100644
index 0000000000..484e503e33
--- /dev/null
+++ b/include/bootmeth.h
@@ -0,0 +1,234 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#ifndef __bootmeth_h
+#define __bootmeth_h
+
+struct blk_desc;
+struct bootflow;
+struct bootflow_iter;
+struct udevice;
+
+/**
+ * struct bootmeth_uc_plat - information the uclass keeps about each bootmeth
+ *
+ * @desc: A long description of the bootmeth
+ */
+struct bootmeth_uc_plat {
+ const char *desc;
+};
+
+/** struct bootmeth_ops - Operations for boot methods */
+struct bootmeth_ops {
+ /**
+ * check_supported() - check if a bootmeth supports this bootflow
+ *
+ * This is optional. If not provided, the bootdev is assumed to be
+ * supported
+ *
+ * The bootmeth can check the bootdev (e.g. to make sure it is a
+ * network device) or the partition information. The following fields
+ * in @iter are available:
+ *
+ * name, dev, state, part
+ * max_part may be set if part != 0 (i.e. there is a valid partition
+ * table). Otherwise max_part is 0
+ * method is available but is the same as @dev
+ * the partition has not yet been read, nor has the filesystem been
+ * checked
+ *
+ * It may update only the flags in @iter
+ *
+ * @dev: Bootmethod device to check against
+ * @iter: On entry, provides bootdev, hwpart, part
+ * Return: 0 if OK, -ENOTSUPP if this bootdev is not supported
+ */
+ int (*check)(struct udevice *dev, struct bootflow_iter *iter);
+
+ /**
+ * read_bootflow() - read a bootflow for a device
+ *
+ * @dev: Bootmethod device to use
+ * @bflow: On entry, provides dev, hwpart, part and method.
+ * Returns updated bootflow if found
+ * Return: 0 if OK, -ve on error
+ */
+ int (*read_bootflow)(struct udevice *dev, struct bootflow *bflow);
+
+ /**
+ * read_file() - read a file needed for a bootflow
+ *
+ * Read a file from the same place as the bootflow came from
+ *
+ * @dev: Bootmethod device to use
+ * @bflow: Bootflow providing info on where to read from
+ * @file_path: Path to file (may be absolute or relative)
+ * @addr: Address to load file
+ * @sizep: On entry provides the maximum permitted size; on exit
+ * returns the size of the file
+ * Return: 0 if OK, -ENOSPC if the file is too large for @sizep, other
+ * -ve value if something else goes wrong
+ */
+ int (*read_file)(struct udevice *dev, struct bootflow *bflow,
+ const char *file_path, ulong addr, ulong *sizep);
+
+ /**
+ * boot() - boot a bootflow
+ *
+ * @dev: Bootmethod device to boot
+ * @bflow: Bootflow to boot
+ * Return: does not return on success, since it should boot the
+ * Operating Systemn. Returns -EFAULT if that fails, -ENOTSUPP if
+ * trying method resulted in finding out that is not actually
+ * supported for this boot and should not be tried again unless
+ * something changes, other -ve on other error
+ */
+ int (*boot)(struct udevice *dev, struct bootflow *bflow);
+};
+
+#define bootmeth_get_ops(dev) ((struct bootmeth_ops *)(dev)->driver->ops)
+
+/**
+ * bootmeth_check() - check if a bootmeth supports this bootflow
+ *
+ * This is optional. If not provided, the bootdev is assumed to be
+ * supported
+ *
+ * The bootmeth can check the bootdev (e.g. to make sure it is a
+ * network device) or the partition information. The following fields
+ * in @iter are available:
+ *
+ * name, dev, state, part
+ * max_part may be set if part != 0 (i.e. there is a valid partition
+ * table). Otherwise max_part is 0
+ * method is available but is the same as @dev
+ * the partition has not yet been read, nor has the filesystem been
+ * checked
+ *
+ * It may update only the flags in @iter
+ *
+ * @dev: Bootmethod device to check against
+ * @iter: On entry, provides bootdev, hwpart, part
+ * Return: 0 if OK, -ENOTSUPP if this bootdev is not supported
+ */
+int bootmeth_check(struct udevice *dev, struct bootflow_iter *iter);
+
+/**
+ * bootmeth_read_bootflow() - set up a bootflow for a device
+ *
+ * @dev: Bootmethod device to check
+ * @bflow: On entry, provides dev, hwpart, part and method.
+ * Returns updated bootflow if found
+ * Return: 0 if OK, -ve on error
+ */
+int bootmeth_read_bootflow(struct udevice *dev, struct bootflow *bflow);
+
+/**
+ * bootmeth_read_file() - read a file needed for a bootflow
+ *
+ * Read a file from the same place as the bootflow came from
+ *
+ * @dev: Bootmethod device to use
+ * @bflow: Bootflow providing info on where to read from
+ * @file_path: Path to file (may be absolute or relative)
+ * @addr: Address to load file
+ * @sizep: On entry provides the maximum permitted size; on exit
+ * returns the size of the file
+ * Return: 0 if OK, -ENOSPC if the file is too large for @sizep, other
+ * -ve value if something else goes wrong
+ */
+int bootmeth_read_file(struct udevice *dev, struct bootflow *bflow,
+ const char *file_path, ulong addr, ulong *sizep);
+
+/**
+ * bootmeth_boot() - boot a bootflow
+ *
+ * @dev: Bootmethod device to boot
+ * @bflow: Bootflow to boot
+ * Return: does not return on success, since it should boot the
+ * Operating Systemn. Returns -EFAULT if that fails, other -ve on
+ * other error
+ */
+int bootmeth_boot(struct udevice *dev, struct bootflow *bflow);
+
+/**
+ * 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);
+
+/**
+ * bootmeth_set_order() - Set the bootmeth order
+ *
+ * This selects the ordering to use for bootmeths
+ *
+ * @order_str: String containing the ordering. This is a comma-separate list of
+ * bootmeth-device names, e.g. "syslinux,efi". If empty then a default ordering
+ * is used, based on the sequence number of devices (i.e. using aliases)
+ * Return: 0 if OK, -ENODEV if an unknown bootmeth is mentioned, -ENOMEM if
+ * out of memory, -ENOENT if there are no bootmeth devices
+ */
+int bootmeth_set_order(const char *order_str);
+
+/**
+ * bootmeth_try_file() - See we can access a given file
+ *
+ * Check for a file with a given name. If found, the filename is allocated in
+ * @bflow
+ *
+ * Sets the state to BOOTFLOWST_FILE on success. It also calls
+ * fs_set_blk_dev_with_part() so that this does not need to be done by the
+ * caller before reading the file.
+ *
+ * @bflow: Information about file to try
+ * @desc: Block descriptor to read from
+ * @prefix: Filename prefix to prepend to @fname (NULL for none)
+ * @fname: Filename to read
+ * Return: 0 if OK, -ENOMEM if not enough memory to allocate bflow->fname,
+ * other -ve value on other error
+ */
+int bootmeth_try_file(struct bootflow *bflow, struct blk_desc *desc,
+ const char *prefix, const char *fname);
+
+/**
+ * bootmeth_alloc_file() - Allocate and read a bootflow file
+ *
+ * Allocates memory for a bootflow file and reads it in. Sets the state to
+ * BOOTFLOWST_READY on success
+ *
+ * Note that fs_set_blk_dev_with_part() must have been called previously.
+ *
+ * @bflow: Information about file to read
+ * @size_limit: Maximum file size to permit
+ * @align: Allocation alignment (1 for unaligned)
+ * Return: 0 if OK, -E2BIG if file is too large, -ENOMEM if out of memory,
+ * other -ve on other error
+ */
+int bootmeth_alloc_file(struct bootflow *bflow, uint size_limit, uint align);
+
+/**
+ * bootmeth_common_read_file() - Common handler for reading a file
+ *
+ * Reads a named file from the same location as the bootflow file.
+ *
+ * @dev: bootmeth device to read from
+ * @bflow: Bootflow information
+ * @file_path: Path to file
+ * @addr: Address to load file to
+ * @sizep: On entry, the maximum file size to accept, on exit the actual file
+ * size read
+ */
+int bootmeth_common_read_file(struct udevice *dev, struct bootflow *bflow,
+ const char *file_path, ulong addr, ulong *sizep);
+
+#endif
diff --git a/include/bootstd.h b/include/bootstd.h
new file mode 100644
index 0000000000..b002365f4f
--- /dev/null
+++ b/include/bootstd.h
@@ -0,0 +1,80 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Standard U-Boot boot framework
+ *
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#ifndef __bootstd_h
+#define __bootstd_h
+
+struct udevice;
+
+/**
+ * struct bootstd_priv - priv data for the bootstd driver
+ *
+ * This is attached to the (only) bootstd device, so there is only one instance
+ * of this struct. It provides overall information about bootdevs and bootflows.
+ *
+ * @prefixes: NULL-terminated list of prefixes to use for bootflow filenames,
+ * e.g. "/", "/boot/"; NULL if none
+ * @bootdev_order: Order to use for bootdevs (or NULL if none), with each item
+ * being a bootdev label, e.g. "mmc2", "mmc1";
+ * @cur_bootdev: Currently selected bootdev (for commands)
+ * @cur_bootflow: Currently selected bootflow (for commands)
+ * @glob_head: Head for the global list of all bootflows across all bootdevs
+ * @bootmeth_count: Number of bootmeth devices in @bootmeth_order
+ * @bootmeth_order: List of bootmeth devices to use, in order, NULL-terminated
+ */
+struct bootstd_priv {
+ const char **prefixes;
+ const char **bootdev_order;
+ struct udevice *cur_bootdev;
+ struct bootflow *cur_bootflow;
+ struct list_head glob_head;
+ int bootmeth_count;
+ struct udevice **bootmeth_order;
+};
+
+/**
+ * bootstd_get_bootdev_order() - Get the boot-order list
+ *
+ * This reads the boot order, e.g. {"mmc0", "mmc2", NULL}
+ *
+ * The list is alloced by the bootstd driver so should not be freed. That is the
+ * reason for all the const stuff in the function signature
+ *
+ * Return: list of string points, terminated by NULL; or NULL if no boot order
+ */
+const char *const *const bootstd_get_bootdev_order(struct udevice *dev);
+
+/**
+ * bootstd_get_prefixes() - Get the filename-prefixes list
+ *
+ * This reads the prefixes, e.g. {"/", "/bpot", NULL}
+ *
+ * The list is alloced by the bootstd driver so should not be freed. That is the
+ * reason for all the const stuff in the function signature
+ *
+ * Return: list of string points, terminated by NULL; or NULL if no boot order
+ */
+const char *const *const bootstd_get_prefixes(struct udevice *dev);
+
+/**
+ * bootstd_get_priv() - Get the (single) state for the bootstd system
+ *
+ * The state holds a global list of all bootflows that have been found.
+ *
+ * Return: 0 if OK, -ve if the uclass does not exist
+ */
+int bootstd_get_priv(struct bootstd_priv **stdp);
+
+/**
+ * bootstd_clear_glob() - Clear the global list of bootflows
+ *
+ * This removes all bootflows globally and across all bootdevs.
+ */
+void bootstd_clear_glob(void);
+
+#endif
diff --git a/include/distro.h b/include/distro.h
new file mode 100644
index 0000000000..2ee145871b
--- /dev/null
+++ b/include/distro.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#ifndef __distro_h
+#define __distro_h
+
+#define DISTRO_FNAME "extlinux/extlinux.conf"
+
+/**
+ * struct distro_info - useful information for distro_getfile()
+ *
+ * @dev: bootmethod device being used to boot
+ * @bflow: bootflow being booted
+ */
+struct distro_info {
+ struct udevice *dev;
+ struct bootflow *bflow;
+ struct cmd_tbl *cmdtp;
+};
+
+#endif
diff --git a/include/dm/device.h b/include/dm/device.h
index e0f86f5df9..b474888d02 100644
--- a/include/dm/device.h
+++ b/include/dm/device.h
@@ -799,7 +799,7 @@ int device_find_first_child_by_uclass(const struct udevice *parent,
struct udevice **devp);
/**
- * device_find_child_by_name() - Find a child by device name
+ * device_find_child_by_namelen() - Find a child by device name
*
* @parent: Parent device to search
* @name: Name to look for
diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h
index 230b1ea528..3ba69ad9a0 100644
--- a/include/dm/uclass-id.h
+++ b/include/dm/uclass-id.h
@@ -38,6 +38,9 @@ enum uclass_id {
UCLASS_AXI, /* AXI bus */
UCLASS_BLK, /* Block device */
UCLASS_BOOTCOUNT, /* Bootcount backing store */
+ UCLASS_BOOTDEV, /* Boot device for locating an OS to boot */
+ UCLASS_BOOTMETH, /* Bootmethod for booting an OS */
+ UCLASS_BOOTSTD, /* Standard boot driver */
UCLASS_BUTTON, /* Button */
UCLASS_CACHE, /* Cache controller */
UCLASS_CLK, /* Clock source, e.g. used by peripherals */
diff --git a/include/dm/uclass-internal.h b/include/dm/uclass-internal.h
index daf856c03c..3ddcdd2143 100644
--- a/include/dm/uclass-internal.h
+++ b/include/dm/uclass-internal.h
@@ -155,6 +155,22 @@ int uclass_find_first_device(enum uclass_id id, struct udevice **devp);
int uclass_find_next_device(struct udevice **devp);
/**
+ * uclass_find_device_by_namelen() - Find uclass device based on ID and name
+ *
+ * This searches for a device with the exactly given name.
+ *
+ * The device is NOT probed, it is merely returned.
+ *
+ * @id: ID to look up
+ * @name: name of a device to find
+ * @len: Length of @name (the uclass driver name must have the same length)
+ * @devp: Returns pointer to device (the first one with the name)
+ * Return: 0 if OK, -ve on error
+ */
+int uclass_find_device_by_namelen(enum uclass_id id, const char *name, int len,
+ struct udevice **devp);
+
+/**
* uclass_find_device_by_name() - Find uclass device based on ID and name
*
* This searches for a device with the exactly given name.
diff --git a/include/dm/uclass.h b/include/dm/uclass.h
index aafe652288..f6c0110b06 100644
--- a/include/dm/uclass.h
+++ b/include/dm/uclass.h
@@ -173,13 +173,13 @@ int uclass_get(enum uclass_id key, struct uclass **ucp);
const char *uclass_get_name(enum uclass_id id);
/**
- * uclass_get_by_name_len() - Look up a uclass by its partial driver name
+ * uclass_get_by_namelen() - Look up a uclass by its driver name
*
* @name: Name to look up
- * @len: Length of the partial name
+ * @len: Length of @name (the uclass driver name must have the same length)
* Return: the associated uclass ID, or UCLASS_INVALID if not found
*/
-enum uclass_id uclass_get_by_name_len(const char *name, int len);
+enum uclass_id uclass_get_by_namelen(const char *name, int len);
/**
* uclass_get_by_name() - Look up a uclass by its driver name
diff --git a/include/env_callback.h b/include/env_callback.h
index 05e9516a0f..d5d2b2fcad 100644
--- a/include/env_callback.h
+++ b/include/env_callback.h
@@ -57,6 +57,12 @@
#define NET_CALLBACKS
#endif
+#ifdef CONFIG_BOOTSTD
+#define BOOTSTD_CALLBACK "bootmeths:bootmeths,"
+#else
+#define BOOTSTD_CALLBACK
+#endif
+
/*
* This list of callback bindings is static, but may be overridden by defining
* a new association in the ".callbacks" environment variable.
@@ -65,6 +71,7 @@
ENV_DOT_ESCAPE ENV_FLAGS_VAR ":flags," \
"baudrate:baudrate," \
NET_CALLBACKS \
+ BOOTSTD_CALLBACK \
"loadaddr:loadaddr," \
SILENT_CALLBACK \
SPLASHIMAGE_CALLBACK \
diff --git a/include/fs.h b/include/fs.h
index e2beba36b9..b43f16a692 100644
--- a/include/fs.h
+++ b/include/fs.h
@@ -57,6 +57,17 @@ int do_ext2load(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
*/
int fs_set_blk_dev(const char *ifname, const char *dev_part_str, int fstype);
+/**
+ * fs_set_type() - Tell fs layer which filesystem type is used
+ *
+ * This is needed when reading from a non-block device such as sandbox. It does
+ * a similar job to fs_set_blk_dev() but just sets the filesystem type instead
+ * of detecting it and loading it on the block device
+ *
+ * @type: Filesystem type to use (FS_TYPE...)
+ */
+void fs_set_type(int type);
+
/*
* fs_set_blk_dev_with_part - Set current block device + partition
*
diff --git a/include/mmc.h b/include/mmc.h
index 6bdcce881d..9b4dc68311 100644
--- a/include/mmc.h
+++ b/include/mmc.h
@@ -956,11 +956,21 @@ int mmc_get_env_dev(void);
* mmc_get_blk_desc() - Get the block descriptor for an MMC device
*
* @mmc: MMC device
- * Return: block device if found, else NULL
+ * Return: block descriptor if found, else NULL
*/
struct blk_desc *mmc_get_blk_desc(struct mmc *mmc);
/**
+ * mmc_get_blk() - Get the block device for an MMC device
+ *
+ * @dev: MMC device
+ * @blkp: Returns pointer to probed block device on sucesss
+ *
+ * Return: 0 on success, -ve on error
+ */
+int mmc_get_blk(struct udevice *dev, struct udevice **blkp);
+
+/**
* mmc_send_ext_csd() - read the extended CSD register
*
* @mmc: MMC device
diff --git a/include/test/suites.h b/include/test/suites.h
index 6553a765c6..ee6858a802 100644
--- a/include/test/suites.h
+++ b/include/test/suites.h
@@ -29,6 +29,8 @@ int cmd_ut_category(const char *name, const char *prefix,
int do_ut_addrmap(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[]);
int do_ut_bootm(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
+int do_ut_bootstd(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[]);
int do_ut_bloblist(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[]);
int do_ut_common(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
diff --git a/include/vsprintf.h b/include/vsprintf.h
index 532ef3650b..e006af200f 100644
--- a/include/vsprintf.h
+++ b/include/vsprintf.h
@@ -13,9 +13,9 @@
/**
* simple_strtoul - convert a string to an unsigned long
*
- * @param cp The string to be converted
- * @param endp Updated to point to the first character not converted
- * @param base The number base to use (0 for the default)
+ * @cp: The string to be converted
+ * @endp: Updated to point to the first character not converted
+ * @base: The number base to use (0 for the default)
* Return: value decoded from string (0 if invalid)
*
* Converts a string to an unsigned long. If there are invalid characters at
@@ -34,8 +34,8 @@ ulong simple_strtoul(const char *cp, char **endp, unsigned int base);
/**
* hex_strtoul - convert a string in hex to an unsigned long
*
- * @param cp The string to be converted
- * @param endp Updated to point to the first character not converted
+ * @cp: The string to be converted
+ * @endp: Updated to point to the first character not converted
* Return: value decoded from string (0 if invalid)
*
* Converts a hex string to an unsigned long. If there are invalid characters at
@@ -47,8 +47,8 @@ unsigned long hextoul(const char *cp, char **endp);
/**
* dec_strtoul - convert a string in decimal to an unsigned long
*
- * @param cp The string to be converted
- * @param endp Updated to point to the first character not converted
+ * @cp: The string to be converted
+ * @endp: Updated to point to the first character not converted
* Return: value decoded from string (0 if invalid)
*
* Converts a decimal string to an unsigned long. If there are invalid
@@ -59,11 +59,11 @@ unsigned long dectoul(const char *cp, char **endp);
/**
* strict_strtoul - convert a string to an unsigned long strictly
- * @param cp The string to be converted
- * @param base The number base to use (0 for the default)
- * @param res The converted result value
- * Return: 0 if conversion is successful and *res is set to the converted
- * value, otherwise it returns -EINVAL and *res is set to 0.
+ * @cp: The string to be converted
+ * @base: The number base to use (0 for the default)
+ * @res: The converted result value
+ * Return: 0 if conversion is successful and `*res` is set to the converted
+ * value, otherwise it returns -EINVAL and `*res` is set to 0.
*
* strict_strtoul converts a string to an unsigned long only if the
* string is really an unsigned long string, any string containing
@@ -98,8 +98,11 @@ long long simple_strtoll(const char *cp, char **endp, unsigned int base);
* Given a string this finds a trailing number on the string and returns it.
* For example, "abc123" would return 123.
*
- * @str: String to exxamine
- * Return: training number if found, else -1
+ * Note that this does not handle a string without a prefix. See dectoul() for
+ * that case.
+ *
+ * @str: String to examine
+ * Return: trailing number if found, else -1
*/
long trailing_strtol(const char *str);
@@ -111,20 +114,38 @@ long trailing_strtol(const char *str);
* characters between @str and @end - 1 are examined. If @end is NULL, it is
* set to str + strlen(str).
*
- * @str: String to exxamine
+ * @str: String to examine
* @end: Pointer to end of string to examine, or NULL to use the
* whole string
- * Return: training number if found, else -1
+ * Return: trailing number if found, else -1
*/
long trailing_strtoln(const char *str, const char *end);
/**
+ * trailing_strtoln_end() - extract trailing integer from a fixed-length string
+ *
+ * Given a fixed-length string this finds a trailing number on the string
+ * and returns it. For example, "abc123" would return 123. Only the
+ * characters between @str and @end - 1 are examined. If @end is NULL, it is
+ * set to str + strlen(str).
+ *
+ * @str: String to examine
+ * @end: Pointer to end of string to examine, or NULL to use the
+ * whole string
+ * @endp: If non-NULL, this is set to point to the character where the
+ * number starts, e.g. for "mmc0" this would be point to the '0'; if no
+ * trailing number is found, it is set to the end of the string
+ * Return: training number if found, else -1
+ */
+long trailing_strtoln_end(const char *str, const char *end, char const **endp);
+
+/**
* panic() - Print a message and reset/hang
*
* Prints a message on the console(s) and then resets. If CONFIG_PANIC_HANG is
* defined, then it will hang instead of resetting.
*
- * @param fmt: printf() format string for message, which should not include
+ * @fmt: printf() format string for message, which should not include
* \n, followed by arguments
*/
void panic(const char *fmt, ...)
@@ -139,16 +160,16 @@ void panic(const char *fmt, ...)
* This function can be used instead of panic() when your board does not
* already use printf(), * to keep code size small.
*
- * @param fmt: string to display, which should not include \n
+ * @str: string to display, which should not include \n
*/
void panic_str(const char *str) __attribute__ ((noreturn));
/**
* Format a string and place it in a buffer
*
- * @param buf The buffer to place the result into
- * @param fmt The format string to use
- * @param ... Arguments for the format string
+ * @buf: The buffer to place the result into
+ * @fmt: The format string to use
+ * @...: Arguments for the format string
*
* The function returns the number of characters written
* into @buf.
@@ -161,9 +182,9 @@ int sprintf(char *buf, const char *fmt, ...)
/**
* Format a string and place it in a buffer (va_list version)
*
- * @param buf The buffer to place the result into
- * @param fmt The format string to use
- * @param args Arguments for the format string
+ * @buf: The buffer to place the result into
+ * @fmt: The format string to use
+ * @args: Arguments for the format string
* Return: the number of characters which have been written into
* the @buf not including the trailing '\0'.
*
@@ -178,7 +199,7 @@ int vsprintf(char *buf, const char *fmt, va_list args);
*
* This returns a static string containing the decimal representation of the
* given value. The returned value may be overwritten by other calls to other
- * simple_... functions, so should be used immediately
+ * simple... functions, so should be used immediately
*
* @val: Value to convert
* Return: string containing the decimal representation of @val
@@ -190,9 +211,9 @@ char *simple_itoa(ulong val);
*
* This returns a static string containing the hexadecimal representation of the
* given value. The returned value may be overwritten by other calls to other
- * simple_... functions, so should be used immediately
+ * simple... functions, so should be used immediately
*
- * @val: Value to convert
+ * @num: Value to convert
* Return: string containing the hexecimal representation of @val
*/
char *simple_xtoa(ulong num);
@@ -200,10 +221,10 @@ char *simple_xtoa(ulong num);
/**
* Format a string and place it in a buffer
*
- * @param buf The buffer to place the result into
- * @param size The size of the buffer, including the trailing null space
- * @param fmt The format string to use
- * @param ... Arguments for the format string
+ * @buf: The buffer to place the result into
+ * @size: The size of the buffer, including the trailing null space
+ * @fmt: The format string to use
+ * @...: Arguments for the format string
* Return: the number of characters which would be
* generated for the given input, excluding the trailing null,
* as per ISO C99. If the return is greater than or equal to
@@ -217,10 +238,10 @@ int snprintf(char *buf, size_t size, const char *fmt, ...)
/**
* Format a string and place it in a buffer
*
- * @param buf The buffer to place the result into
- * @param size The size of the buffer, including the trailing null space
- * @param fmt The format string to use
- * @param ... Arguments for the format string
+ * @buf: The buffer to place the result into
+ * @size: The size of the buffer, including the trailing null space
+ * @fmt: The format string to use
+ * @...: Arguments for the format string
*
* The return value is the number of characters written into @buf not including
* the trailing '\0'. If @size is == 0 the function returns 0.
@@ -233,10 +254,10 @@ int scnprintf(char *buf, size_t size, const char *fmt, ...)
/**
* Format a string and place it in a buffer (base function)
*
- * @param buf The buffer to place the result into
- * @param size The size of the buffer, including the trailing null space
- * @param fmt The format string to use
- * @param args Arguments for the format string
+ * @buf: The buffer to place the result into
+ * @size: The size of the buffer, including the trailing null space
+ * @fmt: The format string to use
+ * @args: Arguments for the format string
* Return: The number characters which would be generated for the given
* input, excluding the trailing '\0', as per ISO C99. Note that fewer
* characters may be written if this number of characters is >= size.
@@ -258,10 +279,10 @@ int vsnprintf(char *buf, size_t size, const char *fmt, va_list args);
/**
* Format a string and place it in a buffer (va_list version)
*
- * @param buf The buffer to place the result into
- * @param size The size of the buffer, including the trailing null space
- * @param fmt The format string to use
- * @param args Arguments for the format string
+ * @buf: The buffer to place the result into
+ * @size: The size of the buffer, including the trailing null space
+ * @fmt: The format string to use
+ * @args: Arguments for the format string
* Return: the number of characters which have been written into
* the @buf not including the trailing '\0'. If @size is == 0 the function
* returns 0.
@@ -278,8 +299,8 @@ int vscnprintf(char *buf, size_t size, const char *fmt, va_list args);
* This prints a value with grouped digits, like 12,345,678 to make it easier
* to read.
*
- * @val: Value to print
- * @digits: Number of digiits to print
+ * @int_val: Value to print
+ * @digits: Number of digiits to print
*/
void print_grouped_ull(unsigned long long int_val, int digits);
@@ -309,9 +330,9 @@ void str_to_upper(const char *in, char *out, size_t len);
/**
* vsscanf - Unformat a buffer into a list of arguments
- * @buf: input buffer
- * @fmt: format of buffer
- * @args: arguments
+ * @inp: input buffer
+ * @fmt0: format of buffer
+ * @ap: arguments
*/
int vsscanf(const char *inp, char const *fmt0, va_list ap);
diff --git a/lib/strto.c b/lib/strto.c
index f191884376..6462d4fddf 100644
--- a/lib/strto.c
+++ b/lib/strto.c
@@ -183,22 +183,33 @@ long long simple_strtoll(const char *cp, char **endp, unsigned int base)
return simple_strtoull(cp, endp, base);
}
-long trailing_strtoln(const char *str, const char *end)
+long trailing_strtoln_end(const char *str, const char *end, char const **endp)
{
const char *p;
if (!end)
end = str + strlen(str);
- if (isdigit(end[-1])) {
- for (p = end - 1; p > str; p--) {
- if (!isdigit(*p))
- return dectoul(p + 1, NULL);
- }
+ p = end - 1;
+ if (p > str && isdigit(*p)) {
+ do {
+ if (!isdigit(p[-1])) {
+ if (endp)
+ *endp = p;
+ return dectoul(p, NULL);
+ }
+ } while (--p > str);
}
+ if (endp)
+ *endp = end;
return -1;
}
+long trailing_strtoln(const char *str, const char *end)
+{
+ return trailing_strtoln_end(str, end, NULL);
+}
+
long trailing_strtol(const char *str)
{
return trailing_strtoln(str, NULL);
diff --git a/net/Kconfig b/net/Kconfig
index ef0aa161f7..964a4fe499 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -25,6 +25,15 @@ config PROT_UDP
Enable a generic udp framework that allows defining a custom
handler for udp protocol.
+config BOOTDEV_ETH
+ bool "Enable bootdev for ethernet"
+ depends on BOOTSTD
+ default y
+ help
+ Provide a bootdev for ethernet so that is it possible to boot
+ an operationg system over the network, using the PXE (Preboot
+ Execution Environment) protocol.
+
config BOOTP_SEND_HOSTNAME
bool "Send hostname to DNS server"
help
diff --git a/net/Makefile b/net/Makefile
index fb3eba840f..6c812502d3 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_CMD_DNS) += dns.o
obj-$(CONFIG_DM_DSA) += dsa-uclass.o
ifdef CONFIG_DM_ETH
obj-$(CONFIG_NET) += eth-uclass.o
+obj-$(CONFIG_$(SPL_TPL_)BOOTDEV_ETH) += eth_bootdev.o
else
obj-$(CONFIG_NET) += eth_legacy.o
endif
diff --git a/net/eth-uclass.c b/net/eth-uclass.c
index 58c308f332..bcefc54ded 100644
--- a/net/eth-uclass.c
+++ b/net/eth-uclass.c
@@ -8,6 +8,7 @@
#define LOG_CATEGORY UCLASS_ETH
#include <common.h>
+#include <bootdev.h>
#include <bootstage.h>
#include <dm.h>
#include <env.h>
@@ -473,6 +474,8 @@ int eth_initialize(void)
static int eth_post_bind(struct udevice *dev)
{
+ int ret;
+
if (strchr(dev->name, ' ')) {
printf("\nError: eth device name \"%s\" has a space!\n",
dev->name);
@@ -482,6 +485,11 @@ static int eth_post_bind(struct udevice *dev)
#ifdef CONFIG_DM_ETH_PHY
eth_phy_binds_nodes(dev);
#endif
+ if (CONFIG_IS_ENABLED(BOOTDEV_ETH)) {
+ ret = bootdev_setup_for_dev(dev, "eth_bootdev");
+ if (ret)
+ return log_msg_ret("bootdev", ret);
+ }
return 0;
}
diff --git a/net/eth_bootdev.c b/net/eth_bootdev.c
new file mode 100644
index 0000000000..b735966d2b
--- /dev/null
+++ b/net/eth_bootdev.c
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Bootdevice for ethernet (uses PXE)
+ *
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <common.h>
+#include <bootdev.h>
+#include <bootflow.h>
+#include <command.h>
+#include <bootmeth.h>
+#include <distro.h>
+#include <dm.h>
+#include <log.h>
+#include <net.h>
+
+static int eth_get_bootflow(struct udevice *dev, struct bootflow_iter *iter,
+ struct bootflow *bflow)
+{
+ char name[60];
+ int ret;
+
+ /* Must be an Ethernet device */
+ ret = bootflow_iter_uses_network(iter);
+ if (ret)
+ return log_msg_ret("net", ret);
+
+ ret = bootmeth_check(bflow->method, iter);
+ if (ret)
+ return log_msg_ret("check", ret);
+
+ /*
+ * Like distro boot, this assumes there is only one Ethernet device.
+ * In this case, that means that @eth is ignored
+ */
+
+ snprintf(name, sizeof(name), "%s.%d", dev->name, iter->part);
+ bflow->name = strdup(name);
+ if (!bflow->name)
+ return log_msg_ret("name", -ENOMEM);
+
+ /*
+ * There is not a direct interface to the network stack so run
+ * everything through the command-line interpreter for now.
+ *
+ * Don't bother checking the result of dhcp. It can fail with:
+ *
+ * DHCP client bound to address 192.168.4.50 (4 ms)
+ * *** Warning: no boot file name; using 'C0A80432.img'
+ * Using smsc95xx_eth device
+ * TFTP from server 192.168.4.1; our IP address is 192.168.4.50
+ * Filename 'C0A80432.img'.
+ * Load address: 0x200000
+ * Loading: *
+ * TFTP error: 'File not found' (1)
+ *
+ * This is not a real failure, since we don't actually care if the
+ * boot file exists.
+ */
+ log_debug("running dhcp\n");
+ run_command("dhcp", 0);
+ bflow->state = BOOTFLOWST_MEDIA;
+
+ /* See distro_pxe_read_bootflow() for the standard impl of this */
+ log_debug("dhcp complete - reading bootflow with method %s\n",
+ bflow->method->name);
+ ret = bootmeth_read_bootflow(bflow->method, bflow);
+ log_debug("reading bootflow returned %d\n", ret);
+ if (ret)
+ return log_msg_ret("method", ret);
+
+ return 0;
+}
+
+static int eth_bootdev_bind(struct udevice *dev)
+{
+ struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev);
+
+ ucp->prio = BOOTDEVP_4_NET_BASE;
+
+ return 0;
+}
+
+struct bootdev_ops eth_bootdev_ops = {
+ .get_bootflow = eth_get_bootflow,
+};
+
+static const struct udevice_id eth_bootdev_ids[] = {
+ { .compatible = "u-boot,bootdev-eth" },
+ { }
+};
+
+U_BOOT_DRIVER(eth_bootdev) = {
+ .name = "eth_bootdev",
+ .id = UCLASS_BOOTDEV,
+ .ops = &eth_bootdev_ops,
+ .bind = eth_bootdev_bind,
+ .of_match = eth_bootdev_ids,
+};
diff --git a/test/Makefile b/test/Makefile
index b3b2902e2e..abd605a435 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_UT_TIME) += time_ut.o
obj-y += ut.o
ifeq ($(CONFIG_SPL_BUILD),)
+obj-$(CONFIG_UNIT_TEST) += boot/
obj-$(CONFIG_UNIT_TEST) += common/
obj-$(CONFIG_UNIT_TEST) += lib/
obj-y += log/
diff --git a/test/boot/Makefile b/test/boot/Makefile
new file mode 100644
index 0000000000..1730792b5f
--- /dev/null
+++ b/test/boot/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0+
+#
+# Copyright 2021 Google LLC
+
+obj-$(CONFIG_BOOTSTD) += bootdev.o bootstd_common.o bootflow.o bootmeth.o
diff --git a/test/boot/bootdev.c b/test/boot/bootdev.c
new file mode 100644
index 0000000000..1c2a79fb10
--- /dev/null
+++ b/test/boot/bootdev.c
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Test for bootdev functions. All start with 'bootdev'
+ *
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <common.h>
+#include <bootstd.h>
+#include <dm.h>
+#include <bootdev.h>
+#include <bootflow.h>
+#include <mapmem.h>
+#include <os.h>
+#include <test/suites.h>
+#include <test/ut.h>
+#include "bootstd_common.h"
+
+/* Allow reseting the USB-started flag */
+extern char usb_started;
+
+/* Check 'bootdev list' command */
+static int bootdev_test_cmd_list(struct unit_test_state *uts)
+{
+ int probed;
+
+ console_record_reset_enable();
+ for (probed = 0; probed < 2; probed++) {
+ int probe_ch = probed ? '+' : ' ';
+
+ ut_assertok(run_command(probed ? "bootdev list -p" :
+ "bootdev list", 0));
+ ut_assert_nextline("Seq Probed Status Uclass Name");
+ ut_assert_nextlinen("---");
+ ut_assert_nextline("%3x [ %c ] %6s %-8s %s", 0, probe_ch, "OK",
+ "mmc", "mmc2.bootdev");
+ ut_assert_nextline("%3x [ %c ] %6s %-8s %s", 1, probe_ch, "OK",
+ "mmc", "mmc1.bootdev");
+ ut_assert_nextline("%3x [ %c ] %6s %-8s %s", 2, probe_ch, "OK",
+ "mmc", "mmc0.bootdev");
+ ut_assert_nextlinen("---");
+ ut_assert_nextline("(3 bootdevs)");
+ ut_assert_console_end();
+ }
+
+ return 0;
+}
+BOOTSTD_TEST(bootdev_test_cmd_list, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
+
+/* Check 'bootdev select' and 'info' commands */
+static int bootdev_test_cmd_select(struct unit_test_state *uts)
+{
+ struct bootstd_priv *std;
+
+ /* get access to the CLI's cur_bootdev */
+ ut_assertok(bootstd_get_priv(&std));
+
+ console_record_reset_enable();
+ ut_asserteq(1, run_command("bootdev info", 0));
+ ut_assert_nextlinen("Please use");
+ ut_assert_console_end();
+
+ /* select by sequence */
+ ut_assertok(run_command("bootdev select 0", 0));
+ ut_assert_console_end();
+
+ ut_assertok(run_command("bootdev info", 0));
+ ut_assert_nextline("Name: mmc2.bootdev");
+ ut_assert_nextline("Sequence: 0");
+ ut_assert_nextline("Status: Probed");
+ ut_assert_nextline("Uclass: mmc");
+ ut_assert_nextline("Bootflows: 0 (0 valid)");
+ ut_assert_console_end();
+
+ /* select by bootdev name */
+ ut_assertok(run_command("bootdev select mmc1.bootdev", 0));
+ ut_assert_console_end();
+ ut_assertnonnull(std->cur_bootdev);
+ ut_asserteq_str("mmc1.bootdev", std->cur_bootdev->name);
+
+ /* select by bootdev label*/
+ ut_assertok(run_command("bootdev select mmc1", 0));
+ ut_assert_console_end();
+ ut_assertnonnull(std->cur_bootdev);
+ ut_asserteq_str("mmc1.bootdev", std->cur_bootdev->name);
+
+ /* deselect */
+ ut_assertok(run_command("bootdev select", 0));
+ ut_assert_console_end();
+ ut_assertnull(std->cur_bootdev);
+
+ ut_asserteq(1, run_command("bootdev info", 0));
+ ut_assert_nextlinen("Please use");
+ ut_assert_console_end();
+
+ return 0;
+}
+BOOTSTD_TEST(bootdev_test_cmd_select, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
+
+/* Check bootdev labels */
+static int bootdev_test_labels(struct unit_test_state *uts)
+{
+ struct udevice *dev, *media;
+
+ ut_assertok(bootdev_find_by_label("mmc2", &dev));
+ ut_asserteq(UCLASS_BOOTDEV, device_get_uclass_id(dev));
+ media = dev_get_parent(dev);
+ ut_asserteq(UCLASS_MMC, device_get_uclass_id(media));
+ ut_asserteq_str("mmc2", media->name);
+
+ /* Check invalid uclass */
+ ut_asserteq(-EINVAL, bootdev_find_by_label("fred0", &dev));
+
+ /* Check unknown sequence number */
+ ut_asserteq(-ENOENT, bootdev_find_by_label("mmc6", &dev));
+
+ return 0;
+}
+BOOTSTD_TEST(bootdev_test_labels, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
+
+/* Check bootdev ordering with the bootdev-order property */
+static int bootdev_test_order(struct unit_test_state *uts)
+{
+ struct bootflow_iter iter;
+ struct bootflow bflow;
+
+ /*
+ * First try the order set by the bootdev-order property
+ * Like all sandbox unit tests this relies on the devicetree setting up
+ * the required devices:
+ *
+ * mmc0 - nothing connected
+ * mmc1 - connected to mmc1.img file
+ * mmc2 - nothing connected
+ */
+ ut_assertok(env_set("boot_targets", NULL));
+ ut_assertok(bootflow_scan_first(&iter, 0, &bflow));
+ ut_asserteq(2, iter.num_devs);
+ ut_asserteq_str("mmc2.bootdev", iter.dev_order[0]->name);
+ ut_asserteq_str("mmc1.bootdev", iter.dev_order[1]->name);
+ bootflow_iter_uninit(&iter);
+
+ /* Use the environment variable to override it */
+ ut_assertok(env_set("boot_targets", "mmc1 mmc2"));
+ ut_assertok(bootflow_scan_first(&iter, 0, &bflow));
+ ut_asserteq(2, iter.num_devs);
+ ut_asserteq_str("mmc1.bootdev", iter.dev_order[0]->name);
+ ut_asserteq_str("mmc2.bootdev", iter.dev_order[1]->name);
+ bootflow_iter_uninit(&iter);
+
+ /*
+ * Now drop both orderings, to check the default (prioriy/sequence)
+ * ordering
+ */
+ ut_assertok(env_set("boot_targets", NULL));
+ ut_assertok(bootstd_test_drop_bootdev_order(uts));
+
+ ut_assertok(bootflow_scan_first(&iter, 0, &bflow));
+ ut_asserteq(3, iter.num_devs);
+ ut_asserteq_str("mmc2.bootdev", iter.dev_order[0]->name);
+ ut_asserteq_str("mmc1.bootdev", iter.dev_order[1]->name);
+ ut_asserteq_str("mmc0.bootdev", iter.dev_order[2]->name);
+
+ /*
+ * Check that adding aliases for the bootdevs works. We just fake it by
+ * setting the sequence numbers directly.
+ */
+ iter.dev_order[0]->seq_ = 0;
+ iter.dev_order[1]->seq_ = 3;
+ iter.dev_order[2]->seq_ = 2;
+ bootflow_iter_uninit(&iter);
+
+ ut_assertok(bootflow_scan_first(&iter, 0, &bflow));
+ ut_asserteq(3, iter.num_devs);
+ ut_asserteq_str("mmc2.bootdev", iter.dev_order[0]->name);
+ ut_asserteq_str("mmc0.bootdev", iter.dev_order[1]->name);
+ ut_asserteq_str("mmc1.bootdev", iter.dev_order[2]->name);
+ bootflow_iter_uninit(&iter);
+
+ return 0;
+}
+BOOTSTD_TEST(bootdev_test_order, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
+
+/* Check bootdev ordering with the uclass priority */
+static int bootdev_test_prio(struct unit_test_state *uts)
+{
+ struct bootdev_uc_plat *ucp;
+ struct bootflow_iter iter;
+ struct bootflow bflow;
+ struct udevice *blk;
+
+ /* Start up USB which gives us three additional bootdevs */
+ usb_started = false;
+ ut_assertok(run_command("usb start", 0));
+
+ ut_assertok(bootstd_test_drop_bootdev_order(uts));
+
+ /* 3 MMC and 3 USB bootdevs: MMC should come before USB */
+ console_record_reset_enable();
+ ut_assertok(bootflow_scan_first(&iter, 0, &bflow));
+ ut_asserteq(6, iter.num_devs);
+ ut_asserteq_str("mmc2.bootdev", iter.dev_order[0]->name);
+ ut_asserteq_str("usb_mass_storage.lun0.bootdev",
+ iter.dev_order[3]->name);
+
+ ut_assertok(bootdev_get_sibling_blk(iter.dev_order[3], &blk));
+ ut_asserteq_str("usb_mass_storage.lun0", blk->name);
+
+ /* adjust the priority of the first USB bootdev to the highest */
+ ucp = dev_get_uclass_plat(iter.dev_order[3]);
+ ucp->prio = 1;
+
+ bootflow_iter_uninit(&iter);
+ ut_assertok(bootflow_scan_first(&iter, 0, &bflow));
+ ut_asserteq(6, iter.num_devs);
+ ut_asserteq_str("usb_mass_storage.lun0.bootdev",
+ iter.dev_order[0]->name);
+ ut_asserteq_str("mmc2.bootdev", iter.dev_order[1]->name);
+
+ return 0;
+}
+BOOTSTD_TEST(bootdev_test_prio, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
diff --git a/test/boot/bootflow.c b/test/boot/bootflow.c
new file mode 100644
index 0000000000..1ebb789e97
--- /dev/null
+++ b/test/boot/bootflow.c
@@ -0,0 +1,400 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Test for bootdev functions. All start with 'bootdev'
+ *
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <common.h>
+#include <bootdev.h>
+#include <bootflow.h>
+#include <bootstd.h>
+#include <dm.h>
+#include <dm/lists.h>
+#include <test/suites.h>
+#include <test/ut.h>
+#include "bootstd_common.h"
+
+/* Check 'bootflow scan/list' commands */
+static int bootflow_cmd(struct unit_test_state *uts)
+{
+ console_record_reset_enable();
+ ut_assertok(run_command("bootdev select 1", 0));
+ ut_assert_console_end();
+ ut_assertok(run_command("bootflow scan -l", 0));
+ ut_assert_nextline("Scanning for bootflows in bootdev 'mmc1.bootdev'");
+ ut_assert_nextline("Seq Method State Uclass Part Name Filename");
+ ut_assert_nextlinen("---");
+ ut_assert_nextline(" 0 syslinux ready mmc 1 mmc1.bootdev.part_1 /extlinux/extlinux.conf");
+ ut_assert_nextlinen("---");
+ ut_assert_nextline("(1 bootflow, 1 valid)");
+ ut_assert_console_end();
+
+ ut_assertok(run_command("bootflow list", 0));
+ ut_assert_nextline("Showing bootflows for bootdev 'mmc1.bootdev'");
+ ut_assert_nextline("Seq Method State Uclass Part Name Filename");
+ ut_assert_nextlinen("---");
+ ut_assert_nextline(" 0 syslinux ready mmc 1 mmc1.bootdev.part_1 /extlinux/extlinux.conf");
+ ut_assert_nextlinen("---");
+ ut_assert_nextline("(1 bootflow, 1 valid)");
+ ut_assert_console_end();
+
+ return 0;
+}
+BOOTSTD_TEST(bootflow_cmd, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
+
+/* Check 'bootflow scan' with a name / label / seq */
+static int bootflow_cmd_label(struct unit_test_state *uts)
+{
+ console_record_reset_enable();
+ ut_assertok(run_command("bootflow scan -l mmc1", 0));
+ ut_assert_nextline("Scanning for bootflows in bootdev 'mmc1.bootdev'");
+ ut_assert_skip_to_line("(1 bootflow, 1 valid)");
+ ut_assert_console_end();
+
+ ut_assertok(run_command("bootflow scan -l mmc0.bootdev", 0));
+ ut_assert_nextline("Scanning for bootflows in bootdev 'mmc0.bootdev'");
+ ut_assert_skip_to_line("(0 bootflows, 0 valid)");
+ ut_assert_console_end();
+
+ ut_assertok(run_command("bootflow scan -l 0", 0));
+ ut_assert_nextline("Scanning for bootflows in bootdev 'mmc2.bootdev'");
+ ut_assert_skip_to_line("(0 bootflows, 0 valid)");
+ ut_assert_console_end();
+
+ return 0;
+}
+BOOTSTD_TEST(bootflow_cmd_label, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
+
+/* Check 'bootflow scan/list' commands using all bootdevs */
+static int bootflow_cmd_glob(struct unit_test_state *uts)
+{
+ ut_assertok(bootstd_test_drop_bootdev_order(uts));
+
+ console_record_reset_enable();
+ ut_assertok(run_command("bootflow scan -l", 0));
+ ut_assert_nextline("Scanning for bootflows in all bootdevs");
+ ut_assert_nextline("Seq Method State Uclass Part Name Filename");
+ ut_assert_nextlinen("---");
+ ut_assert_nextline("Scanning bootdev 'mmc2.bootdev':");
+ ut_assert_nextline("Scanning bootdev 'mmc1.bootdev':");
+ ut_assert_nextline(" 0 syslinux ready mmc 1 mmc1.bootdev.part_1 /extlinux/extlinux.conf");
+ ut_assert_nextline("Scanning bootdev 'mmc0.bootdev':");
+ ut_assert_nextline("No more bootdevs");
+ ut_assert_nextlinen("---");
+ ut_assert_nextline("(1 bootflow, 1 valid)");
+ ut_assert_console_end();
+
+ ut_assertok(run_command("bootflow list", 0));
+ ut_assert_nextline("Showing all bootflows");
+ ut_assert_nextline("Seq Method State Uclass Part Name Filename");
+ ut_assert_nextlinen("---");
+ ut_assert_nextline(" 0 syslinux ready mmc 1 mmc1.bootdev.part_1 /extlinux/extlinux.conf");
+ ut_assert_nextlinen("---");
+ ut_assert_nextline("(1 bootflow, 1 valid)");
+ ut_assert_console_end();
+
+ return 0;
+}
+BOOTSTD_TEST(bootflow_cmd_glob, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
+
+/* Check 'bootflow scan -e' */
+static int bootflow_cmd_scan_e(struct unit_test_state *uts)
+{
+ ut_assertok(bootstd_test_drop_bootdev_order(uts));
+
+ console_record_reset_enable();
+ ut_assertok(run_command("bootflow scan -ale", 0));
+ ut_assert_nextline("Scanning for bootflows in all bootdevs");
+ ut_assert_nextline("Seq Method State Uclass Part Name Filename");
+ ut_assert_nextlinen("---");
+ ut_assert_nextline("Scanning bootdev 'mmc2.bootdev':");
+ ut_assert_nextline(" 0 syslinux media mmc 0 mmc2.bootdev.whole <NULL>");
+ ut_assert_nextline(" ** No partition found, err=-93");
+ ut_assert_nextline(" 1 efi media mmc 0 mmc2.bootdev.whole <NULL>");
+ ut_assert_nextline(" ** No partition found, err=-93");
+
+ ut_assert_nextline("Scanning bootdev 'mmc1.bootdev':");
+ ut_assert_nextline(" 2 syslinux media mmc 0 mmc1.bootdev.whole <NULL>");
+ ut_assert_nextline(" ** No partition found, err=-2");
+ ut_assert_nextline(" 3 efi media mmc 0 mmc1.bootdev.whole <NULL>");
+ ut_assert_nextline(" ** No partition found, err=-2");
+ ut_assert_nextline(" 4 syslinux ready mmc 1 mmc1.bootdev.part_1 /extlinux/extlinux.conf");
+ ut_assert_nextline(" 5 efi fs mmc 1 mmc1.bootdev.part_1 efi/boot/bootsbox.efi");
+
+ ut_assert_skip_to_line("Scanning bootdev 'mmc0.bootdev':");
+ ut_assert_skip_to_line(" 3f efi media mmc 0 mmc0.bootdev.whole <NULL>");
+ ut_assert_nextline(" ** No partition found, err=-93");
+ ut_assert_nextline("No more bootdevs");
+ ut_assert_nextlinen("---");
+ ut_assert_nextline("(64 bootflows, 1 valid)");
+ ut_assert_console_end();
+
+ ut_assertok(run_command("bootflow list", 0));
+ ut_assert_nextline("Showing all bootflows");
+ ut_assert_nextline("Seq Method State Uclass Part Name Filename");
+ ut_assert_nextlinen("---");
+ ut_assert_nextline(" 0 syslinux media mmc 0 mmc2.bootdev.whole <NULL>");
+ ut_assert_nextline(" 1 efi media mmc 0 mmc2.bootdev.whole <NULL>");
+ ut_assert_skip_to_line(" 4 syslinux ready mmc 1 mmc1.bootdev.part_1 /extlinux/extlinux.conf");
+ ut_assert_skip_to_line(" 3f efi media mmc 0 mmc0.bootdev.whole <NULL>");
+ ut_assert_nextlinen("---");
+ ut_assert_nextline("(64 bootflows, 1 valid)");
+ ut_assert_console_end();
+
+ return 0;
+}
+BOOTSTD_TEST(bootflow_cmd_scan_e, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
+
+/* Check 'bootflow info' */
+static int bootflow_cmd_info(struct unit_test_state *uts)
+{
+ console_record_reset_enable();
+ ut_assertok(run_command("bootdev select 1", 0));
+ ut_assert_console_end();
+ ut_assertok(run_command("bootflow scan", 0));
+ ut_assert_console_end();
+ ut_assertok(run_command("bootflow select 0", 0));
+ ut_assert_console_end();
+ ut_assertok(run_command("bootflow info", 0));
+ ut_assert_nextline("Name: mmc1.bootdev.part_1");
+ ut_assert_nextline("Device: mmc1.bootdev");
+ ut_assert_nextline("Block dev: mmc1.blk");
+ ut_assert_nextline("Method: syslinux");
+ ut_assert_nextline("State: ready");
+ ut_assert_nextline("Partition: 1");
+ ut_assert_nextline("Subdir: (none)");
+ ut_assert_nextline("Filename: /extlinux/extlinux.conf");
+ ut_assert_nextlinen("Buffer: ");
+ ut_assert_nextline("Size: 253 (595 bytes)");
+ ut_assert_nextline("Error: 0");
+ ut_assert_console_end();
+
+ ut_assertok(run_command("bootflow info -d", 0));
+ ut_assert_nextline("Name: mmc1.bootdev.part_1");
+ ut_assert_skip_to_line("Error: 0");
+ ut_assert_nextline("Contents:");
+ ut_assert_nextline("%s", "");
+ ut_assert_nextline("# extlinux.conf generated by appliance-creator");
+ ut_assert_skip_to_line(" initrd /initramfs-5.3.7-301.fc31.armv7hl.img");
+ ut_assert_console_end();
+
+ return 0;
+}
+BOOTSTD_TEST(bootflow_cmd_info, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
+
+/* Check 'bootflow scan -b' to boot the first available bootdev */
+static int bootflow_scan_boot(struct unit_test_state *uts)
+{
+ console_record_reset_enable();
+ ut_assertok(run_command("bootflow scan -b", 0));
+ ut_assert_nextline(
+ "** Booting bootflow 'mmc1.bootdev.part_1' with syslinux");
+ ut_assert_nextline("Ignoring unknown command: ui");
+
+ /*
+ * We expect it to get through to boot although sandbox always returns
+ * -EFAULT as it cannot actually boot the kernel
+ */
+ ut_assert_skip_to_line("sandbox: continuing, as we cannot run Linux");
+ ut_assert_nextline("Boot failed (err=-14)");
+ ut_assert_console_end();
+
+ return 0;
+}
+BOOTSTD_TEST(bootflow_scan_boot, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
+
+/* Check iterating through available bootflows */
+static int bootflow_iter(struct unit_test_state *uts)
+{
+ struct bootflow_iter iter;
+ struct bootflow bflow;
+
+ bootstd_clear_glob();
+
+ /* The first device is mmc2.bootdev which has no media */
+ ut_asserteq(-EPROTONOSUPPORT,
+ bootflow_scan_first(&iter, BOOTFLOWF_ALL, &bflow));
+ ut_asserteq(2, iter.num_methods);
+ ut_asserteq(0, iter.cur_method);
+ ut_asserteq(0, iter.part);
+ ut_asserteq(0, iter.max_part);
+ ut_asserteq_str("syslinux", iter.method->name);
+ ut_asserteq(0, bflow.err);
+
+ /*
+ * This shows MEDIA even though there is none, since int
+ * bootdev_find_in_blk() we call part_get_info() which returns
+ * -EPROTONOSUPPORT. Ideally it would return -EEOPNOTSUPP and we would
+ * know.
+ */
+ ut_asserteq(BOOTFLOWST_MEDIA, bflow.state);
+
+ ut_asserteq(-EPROTONOSUPPORT, bootflow_scan_next(&iter, &bflow));
+ ut_asserteq(2, iter.num_methods);
+ ut_asserteq(1, iter.cur_method);
+ ut_asserteq(0, iter.part);
+ ut_asserteq(0, iter.max_part);
+ ut_asserteq_str("efi", iter.method->name);
+ ut_asserteq(0, bflow.err);
+ ut_asserteq(BOOTFLOWST_MEDIA, bflow.state);
+ bootflow_free(&bflow);
+
+ /* The next device is mmc1.bootdev - at first we use the whole device */
+ ut_asserteq(-ENOENT, bootflow_scan_next(&iter, &bflow));
+ ut_asserteq(2, iter.num_methods);
+ ut_asserteq(0, iter.cur_method);
+ ut_asserteq(0, iter.part);
+ ut_asserteq(0x1e, iter.max_part);
+ ut_asserteq_str("syslinux", iter.method->name);
+ ut_asserteq(0, bflow.err);
+ ut_asserteq(BOOTFLOWST_MEDIA, bflow.state);
+ bootflow_free(&bflow);
+
+ ut_asserteq(-ENOENT, bootflow_scan_next(&iter, &bflow));
+ ut_asserteq(2, iter.num_methods);
+ ut_asserteq(1, iter.cur_method);
+ ut_asserteq(0, iter.part);
+ ut_asserteq(0x1e, iter.max_part);
+ ut_asserteq_str("efi", iter.method->name);
+ ut_asserteq(0, bflow.err);
+ ut_asserteq(BOOTFLOWST_MEDIA, bflow.state);
+ bootflow_free(&bflow);
+
+ /* Then more to partition 1 where we find something */
+ ut_assertok(bootflow_scan_next(&iter, &bflow));
+ ut_asserteq(2, iter.num_methods);
+ ut_asserteq(0, iter.cur_method);
+ ut_asserteq(1, iter.part);
+ ut_asserteq(0x1e, iter.max_part);
+ ut_asserteq_str("syslinux", iter.method->name);
+ ut_asserteq(0, bflow.err);
+ ut_asserteq(BOOTFLOWST_READY, bflow.state);
+ bootflow_free(&bflow);
+
+ ut_asserteq(-ENOENT, bootflow_scan_next(&iter, &bflow));
+ ut_asserteq(2, iter.num_methods);
+ ut_asserteq(1, iter.cur_method);
+ ut_asserteq(1, iter.part);
+ ut_asserteq(0x1e, iter.max_part);
+ ut_asserteq_str("efi", iter.method->name);
+ ut_asserteq(0, bflow.err);
+ ut_asserteq(BOOTFLOWST_FS, bflow.state);
+ bootflow_free(&bflow);
+
+ /* Then more to partition 2 which doesn't exist */
+ ut_asserteq(-ENOENT, bootflow_scan_next(&iter, &bflow));
+ ut_asserteq(2, iter.num_methods);
+ ut_asserteq(0, iter.cur_method);
+ ut_asserteq(2, iter.part);
+ ut_asserteq(0x1e, iter.max_part);
+ ut_asserteq_str("syslinux", iter.method->name);
+ ut_asserteq(0, bflow.err);
+ ut_asserteq(BOOTFLOWST_MEDIA, bflow.state);
+ bootflow_free(&bflow);
+
+ bootflow_iter_uninit(&iter);
+
+ ut_assert_console_end();
+
+ return 0;
+}
+BOOTSTD_TEST(bootflow_iter, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
+
+/* Check using the system bootdev */
+static int bootflow_system(struct unit_test_state *uts)
+{
+ struct udevice *bootstd, *dev;
+
+ /* Add the EFI bootmgr driver */
+ ut_assertok(uclass_first_device_err(UCLASS_BOOTSTD, &bootstd));
+ ut_assertok(device_bind_driver(bootstd, "bootmeth_efi_mgr", "bootmgr",
+ &dev));
+
+ /* Add the system bootdev that it uses */
+ ut_assertok(device_bind_driver(bootstd, "system_bootdev",
+ "system-bootdev", &dev));
+
+ ut_assertok(bootstd_test_drop_bootdev_order(uts));
+
+ /* We should get a single 'bootmgr' method right at the end */
+ bootstd_clear_glob();
+ console_record_reset_enable();
+ ut_assertok(run_command("bootflow scan -l", 0));
+ ut_assert_skip_to_line(" 1 bootmgr ready bootstd 0 <NULL> <NULL>");
+ ut_assert_nextline("No more bootdevs");
+ ut_assert_skip_to_line("(2 bootflows, 2 valid)");
+ ut_assert_console_end();
+
+ return 0;
+}
+BOOTSTD_TEST(bootflow_system, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
+
+/* Check disabling a bootmethod if it requests it */
+static int bootflow_iter_disable(struct unit_test_state *uts)
+{
+ struct udevice *bootstd, *dev;
+ struct bootflow_iter iter;
+ struct bootflow bflow;
+ int i;
+
+ /* Add the EFI bootmgr driver */
+ ut_assertok(uclass_first_device_err(UCLASS_BOOTSTD, &bootstd));
+ ut_assertok(device_bind_driver(bootstd, "bootmeth_sandbox", "sandbox",
+ &dev));
+
+ /* Add the system bootdev that it uses */
+ ut_assertok(device_bind_driver(bootstd, "system_bootdev",
+ "system-bootdev", &dev));
+
+ ut_assertok(bootstd_test_drop_bootdev_order(uts));
+
+ bootstd_clear_glob();
+ ut_assertok(run_command("bootflow scan -lb", 0));
+
+ /* Try to boot the bootmgr flow, which will fail */
+ console_record_reset_enable();
+ ut_assertok(bootflow_scan_first(&iter, 0, &bflow));
+ ut_asserteq(3, iter.num_methods);
+ ut_asserteq_str("sandbox", iter.method->name);
+ ut_asserteq(-ENOTSUPP, bootflow_run_boot(&iter, &bflow));
+
+ ut_assert_skip_to_line("Boot method 'sandbox' failed and will not be retried");
+ ut_assert_console_end();
+
+ /* Check that the sandbox bootmeth has been removed */
+ ut_asserteq(2, iter.num_methods);
+ for (i = 0; i < iter.num_methods; i++)
+ ut_assert(strcmp("sandbox", iter.method_order[i]->name));
+
+ return 0;
+}
+BOOTSTD_TEST(bootflow_iter_disable, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
+
+/* Check 'bootflow boot' to boot a selected bootflow */
+static int bootflow_cmd_boot(struct unit_test_state *uts)
+{
+ console_record_reset_enable();
+ ut_assertok(run_command("bootdev select 1", 0));
+ ut_assert_console_end();
+ ut_assertok(run_command("bootflow scan", 0));
+ ut_assert_console_end();
+ ut_assertok(run_command("bootflow select 0", 0));
+ ut_assert_console_end();
+ ut_asserteq(1, run_command("bootflow boot", 0));
+ ut_assert_nextline(
+ "** Booting bootflow 'mmc1.bootdev.part_1' with syslinux");
+ ut_assert_nextline("Ignoring unknown command: ui");
+
+ /*
+ * We expect it to get through to boot although sandbox always returns
+ * -EFAULT as it cannot actually boot the kernel
+ */
+ ut_assert_skip_to_line("sandbox: continuing, as we cannot run Linux");
+ ut_assert_nextline("Boot failed (err=-14)");
+ ut_assert_console_end();
+
+ return 0;
+}
+BOOTSTD_TEST(bootflow_cmd_boot, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
diff --git a/test/boot/bootmeth.c b/test/boot/bootmeth.c
new file mode 100644
index 0000000000..07776c5368
--- /dev/null
+++ b/test/boot/bootmeth.c
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Test for bootdev functions. All start with 'bootdev'
+ *
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <common.h>
+#include <bootstd.h>
+#include <test/suites.h>
+#include <test/ut.h>
+#include "bootstd_common.h"
+
+/* Check 'bootmeth list' command */
+static int bootmeth_cmd_list(struct unit_test_state *uts)
+{
+ console_record_reset_enable();
+ ut_assertok(run_command("bootmeth list", 0));
+ ut_assert_nextline("Order Seq Name Description");
+ ut_assert_nextlinen("---");
+ ut_assert_nextline(" 0 0 syslinux Syslinux boot from a block device");
+ ut_assert_nextline(" 1 1 efi EFI boot from an .efi file");
+ ut_assert_nextlinen("---");
+ ut_assert_nextline("(2 bootmeths)");
+ ut_assert_console_end();
+
+ return 0;
+}
+BOOTSTD_TEST(bootmeth_cmd_list, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
+
+/* Check 'bootmeth order' command */
+static int bootmeth_cmd_order(struct unit_test_state *uts)
+{
+ /* Select just one bootmethod */
+ console_record_reset_enable();
+ ut_assertok(run_command("bootmeth order syslinux", 0));
+ ut_assert_console_end();
+ ut_assertnonnull(env_get("bootmeths"));
+ ut_asserteq_str("syslinux", env_get("bootmeths"));
+
+ /* Only that one should be listed */
+ ut_assertok(run_command("bootmeth list", 0));
+ ut_assert_nextline("Order Seq Name Description");
+ ut_assert_nextlinen("---");
+ ut_assert_nextline(" 0 0 syslinux Syslinux boot from a block device");
+ ut_assert_nextlinen("---");
+ ut_assert_nextline("(1 bootmeth)");
+ ut_assert_console_end();
+
+ /* Check the -a flag, efi should show as not in the order ("-") */
+ ut_assertok(run_command("bootmeth list -a", 0));
+ ut_assert_nextline("Order Seq Name Description");
+ ut_assert_nextlinen("---");
+ ut_assert_nextline(" 0 0 syslinux Syslinux boot from a block device");
+ ut_assert_nextline(" - 1 efi EFI boot from an .efi file");
+ ut_assert_nextlinen("---");
+ ut_assert_nextline("(2 bootmeths)");
+ ut_assert_console_end();
+
+ /* Check the -a flag with the reverse order */
+ ut_assertok(run_command("bootmeth order \"efi syslinux\"", 0));
+ ut_assert_console_end();
+ ut_assertok(run_command("bootmeth list -a", 0));
+ ut_assert_nextline("Order Seq Name Description");
+ ut_assert_nextlinen("---");
+ ut_assert_nextline(" 1 0 syslinux Syslinux boot from a block device");
+ ut_assert_nextline(" 0 1 efi EFI boot from an .efi file");
+ ut_assert_nextlinen("---");
+ ut_assert_nextline("(2 bootmeths)");
+ ut_assert_console_end();
+
+ /* Now reset the order to empty, which should show all of them again */
+ ut_assertok(run_command("bootmeth order", 0));
+ ut_assert_console_end();
+ ut_assertnull(env_get("bootmeths"));
+ ut_assertok(run_command("bootmeth list", 0));
+ ut_assert_skip_to_line("(2 bootmeths)");
+
+ /* Try reverse order */
+ ut_assertok(run_command("bootmeth order \"efi syslinux\"", 0));
+ ut_assert_console_end();
+ ut_assertok(run_command("bootmeth list", 0));
+ ut_assert_nextline("Order Seq Name Description");
+ ut_assert_nextlinen("---");
+ ut_assert_nextline(" 0 1 efi EFI boot from an .efi file");
+ ut_assert_nextline(" 1 0 syslinux Syslinux boot from a block device");
+ ut_assert_nextlinen("---");
+ ut_assert_nextline("(2 bootmeths)");
+ ut_assertnonnull(env_get("bootmeths"));
+ ut_asserteq_str("efi syslinux", env_get("bootmeths"));
+ ut_assert_console_end();
+
+ return 0;
+}
+BOOTSTD_TEST(bootmeth_cmd_order, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
+
+/* Check 'bootmeths' env var */
+static int bootmeth_env(struct unit_test_state *uts)
+{
+ struct bootstd_priv *std;
+
+ ut_assertok(bootstd_get_priv(&std));
+
+ /* Select just one bootmethod */
+ console_record_reset_enable();
+ ut_assertok(env_set("bootmeths", "syslinux"));
+ ut_asserteq(1, std->bootmeth_count);
+
+ /* Select an invalid bootmethod */
+ ut_asserteq(1, run_command("setenv bootmeths fred", 0));
+ ut_assert_nextline("Unknown bootmeth 'fred'");
+ ut_assert_nextlinen("## Error inserting");
+ ut_assert_console_end();
+
+ ut_assertok(env_set("bootmeths", "efi syslinux"));
+ ut_asserteq(2, std->bootmeth_count);
+ ut_assert_console_end();
+
+ return 0;
+}
+BOOTSTD_TEST(bootmeth_env, UT_TESTF_DM | UT_TESTF_SCAN_FDT);
diff --git a/test/boot/bootstd_common.c b/test/boot/bootstd_common.c
new file mode 100644
index 0000000000..05347d8710
--- /dev/null
+++ b/test/boot/bootstd_common.c
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Test for bootdev functions. All start with 'bootdev'
+ *
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <common.h>
+#include <bootstd.h>
+#include <dm.h>
+#include <test/suites.h>
+#include <test/ut.h>
+#include "bootstd_common.h"
+
+int bootstd_test_drop_bootdev_order(struct unit_test_state *uts)
+{
+ struct bootstd_priv *priv;
+ struct udevice *bootstd;
+
+ ut_assertok(uclass_first_device_err(UCLASS_BOOTSTD, &bootstd));
+ priv = dev_get_priv(bootstd);
+ priv->bootdev_order = NULL;
+
+ return 0;
+}
+
+int do_ut_bootstd(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
+{
+ struct unit_test *tests = UNIT_TEST_SUITE_START(bootstd_test);
+ const int n_ents = UNIT_TEST_SUITE_COUNT(bootstd_test);
+
+ return cmd_ut_category("bootstd", "bootstd_test_",
+ tests, n_ents, argc, argv);
+}
diff --git a/test/boot/bootstd_common.h b/test/boot/bootstd_common.h
new file mode 100644
index 0000000000..676ef0a57f
--- /dev/null
+++ b/test/boot/bootstd_common.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Common header file for bootdev, bootflow, bootmeth tests
+ *
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#ifndef __bootstd_common_h
+#define __bootstd_common_h
+
+/* Declare a new bootdev test */
+#define BOOTSTD_TEST(_name, _flags) \
+ UNIT_TEST(_name, _flags, bootstd_test)
+
+struct unit_test_state;
+
+/**
+ * bootstd_test_drop_bootdev_order() - Remove the existing boot order
+ *
+ * Drop the boot order so that all bootdevs are used in their alias order
+ *
+ * @uts: Unit test state to use for ut_assert...() functions
+ */
+int bootstd_test_drop_bootdev_order(struct unit_test_state *uts);
+
+#endif
diff --git a/test/cmd_ut.c b/test/cmd_ut.c
index 90b260f72d..67a13ee32b 100644
--- a/test/cmd_ut.c
+++ b/test/cmd_ut.c
@@ -28,6 +28,10 @@ int cmd_ut_category(const char *name, const char *prefix,
static struct cmd_tbl cmd_ut_sub[] = {
U_BOOT_CMD_MKENT(all, CONFIG_SYS_MAXARGS, 1, do_ut_all, "", ""),
+#ifdef CONFIG_BOOTSTD
+ U_BOOT_CMD_MKENT(bootstd, CONFIG_SYS_MAXARGS, 1, do_ut_bootstd,
+ "", ""),
+#endif
U_BOOT_CMD_MKENT(common, CONFIG_SYS_MAXARGS, 1, do_ut_common, "", ""),
#if defined(CONFIG_UT_DM)
U_BOOT_CMD_MKENT(dm, CONFIG_SYS_MAXARGS, 1, do_ut_dm, "", ""),
@@ -115,6 +119,9 @@ static char ut_help_text[] =
"ut bloblist - Test bloblist implementation\n"
"ut compression - Test compressors and bootm decompression\n"
#endif
+#ifdef CONFIG_BOOTSTD
+ "ut bootstd - Test standard boot implementation\n"
+#endif
#ifdef CONFIG_UT_DM
"ut dm [test-name]\n"
#endif
diff --git a/test/dm/blk.c b/test/dm/blk.c
index 8556cc7159..85c3a3bd45 100644
--- a/test/dm/blk.c
+++ b/test/dm/blk.c
@@ -15,6 +15,9 @@
DECLARE_GLOBAL_DATA_PTR;
+/* Allow resetting the USB-started flag */
+extern char usb_started;
+
/* Test that block devices can be created */
static int dm_test_blk_base(struct unit_test_state *uts)
{
@@ -66,8 +69,11 @@ static int dm_test_blk_usb(struct unit_test_state *uts)
struct udevice *usb_dev, *dev;
struct blk_desc *dev_desc;
+ usb_started = false;
+
/* Get a flash device */
state_set_skip_delays(true);
+ ut_assertok(usb_stop());
ut_assertok(usb_init());
ut_assertok(uclass_get_device(UCLASS_MASS_STORAGE, 0, &usb_dev));
ut_assertok(blk_get_device_by_str("usb", "0", &dev_desc));
diff --git a/test/dm/core.c b/test/dm/core.c
index 0ce0b3c4a2..ebd504427d 100644
--- a/test/dm/core.c
+++ b/test/dm/core.c
@@ -1161,6 +1161,8 @@ static int dm_test_uclass_names(struct unit_test_state *uts)
ut_asserteq_str("test", uclass_get_name(UCLASS_TEST));
ut_asserteq(UCLASS_TEST, uclass_get_by_name("test"));
+ ut_asserteq(UCLASS_SPI, uclass_get_by_name("spi"));
+
return 0;
}
DM_TEST(dm_test_uclass_names, UT_TESTF_SCAN_PDATA);
@@ -1258,3 +1260,18 @@ static int dm_test_get_stats(struct unit_test_state *uts)
return 0;
}
DM_TEST(dm_test_get_stats, UT_TESTF_SCAN_FDT);
+
+/* Test uclass_find_device_by_name() */
+static int dm_test_uclass_find_device(struct unit_test_state *uts)
+{
+ struct udevice *dev;
+
+ ut_assertok(uclass_find_device_by_name(UCLASS_I2C, "i2c@0", &dev));
+ ut_asserteq(-ENODEV,
+ uclass_find_device_by_name(UCLASS_I2C, "i2c@0x", &dev));
+ ut_assertok(uclass_find_device_by_namelen(UCLASS_I2C, "i2c@0x", 5,
+ &dev));
+
+ return 0;
+}
+DM_TEST(dm_test_uclass_find_device, UT_TESTF_SCAN_FDT);
diff --git a/test/dm/fastboot.c b/test/dm/fastboot.c
index e7f8c362b8..758538d0e8 100644
--- a/test/dm/fastboot.c
+++ b/test/dm/fastboot.c
@@ -81,9 +81,9 @@ static int dm_test_fastboot_mmc_part(struct unit_test_state *uts)
&part_info, response));
ut_asserteq(0, fastboot_mmc_get_part_info("0.0:0", &fb_dev_desc,
&part_info, response));
- ut_asserteq(0, fastboot_mmc_get_part_info("1", &fb_dev_desc,
+ ut_asserteq(0, fastboot_mmc_get_part_info("2", &fb_dev_desc,
&part_info, response));
- ut_asserteq(0, fastboot_mmc_get_part_info("1.0", &fb_dev_desc,
+ ut_asserteq(0, fastboot_mmc_get_part_info("2.0", &fb_dev_desc,
&part_info, response));
ut_asserteq(1, fastboot_mmc_get_part_info(":1", &fb_dev_desc,
&part_info, response));
diff --git a/test/print_ut.c b/test/print_ut.c
index 247011f2db..47a6ce5784 100644
--- a/test/print_ut.c
+++ b/test/print_ut.c
@@ -345,26 +345,6 @@ static int print_do_hex_dump(struct unit_test_state *uts)
}
PRINT_TEST(print_do_hex_dump, UT_TESTF_CONSOLE_REC);
-static int print_itoa(struct unit_test_state *uts)
-{
- ut_asserteq_str("123", simple_itoa(123));
- ut_asserteq_str("0", simple_itoa(0));
- ut_asserteq_str("2147483647", simple_itoa(0x7fffffff));
- ut_asserteq_str("4294967295", simple_itoa(0xffffffff));
-
- /* Use #ifdef here to avoid a compiler warning on 32-bit machines */
-#ifdef CONFIG_PHYS_64BIT
- if (sizeof(ulong) == 8) {
- ut_asserteq_str("9223372036854775807",
- simple_itoa((1UL << 63) - 1));
- ut_asserteq_str("18446744073709551615", simple_itoa(-1));
- }
-#endif /* CONFIG_PHYS_64BIT */
-
- return 0;
-}
-PRINT_TEST(print_itoa, 0);
-
static int snprint(struct unit_test_state *uts)
{
char buf[10] = "xxxxxxxxx";
@@ -391,26 +371,6 @@ static int snprint(struct unit_test_state *uts)
}
PRINT_TEST(snprint, 0);
-static int print_xtoa(struct unit_test_state *uts)
-{
- ut_asserteq_str("7f", simple_xtoa(127));
- ut_asserteq_str("00", simple_xtoa(0));
- ut_asserteq_str("7fffffff", simple_xtoa(0x7fffffff));
- ut_asserteq_str("ffffffff", simple_xtoa(0xffffffff));
-
- /* Use #ifdef here to avoid a compiler warning on 32-bit machines */
-#ifdef CONFIG_PHYS_64BIT
- if (sizeof(ulong) == 8) {
- ut_asserteq_str("7fffffffffffffff",
- simple_xtoa((1UL << 63) - 1));
- ut_asserteq_str("ffffffffffffffff", simple_xtoa(-1));
- }
-#endif /* CONFIG_PHYS_64BIT */
-
- return 0;
-}
-PRINT_TEST(print_xtoa, 0);
-
int do_ut_print(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
{
struct unit_test *tests = UNIT_TEST_SUITE_START(print_test);
diff --git a/test/py/tests/bootstd/mmc1.img.xz b/test/py/tests/bootstd/mmc1.img.xz
new file mode 100644
index 0000000000..4e7f39b830
--- /dev/null
+++ b/test/py/tests/bootstd/mmc1.img.xz
Binary files differ
diff --git a/test/py/tests/test_ut.py b/test/py/tests/test_ut.py
index 01c2b3ffa1..35fb393c1f 100644
--- a/test/py/tests/test_ut.py
+++ b/test/py/tests/test_ut.py
@@ -1,9 +1,102 @@
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
+import gzip
+import os
import os.path
import pytest
+import u_boot_utils
+
+def mkdir_cond(dirname):
+ """Create a directory if it doesn't already exist
+
+ Args:
+ dirname: Name of directory to create
+ """
+ if not os.path.exists(dirname):
+ os.mkdir(dirname)
+
+def setup_bootflow_image(u_boot_console):
+ """Create a 20MB disk image with a single FAT partition"""
+ cons = u_boot_console
+ fname = os.path.join(cons.config.source_dir, 'mmc1.img')
+ mnt = os.path.join(cons.config.persistent_data_dir, 'mnt')
+ mkdir_cond(mnt)
+
+ u_boot_utils.run_and_log(cons, 'qemu-img create %s 20M' % fname)
+ u_boot_utils.run_and_log(cons, 'sudo sfdisk %s' % fname,
+ stdin=b'type=c')
+
+ loop = None
+ mounted = False
+ complete = False
+ try:
+ out = u_boot_utils.run_and_log(cons,
+ 'sudo losetup --show -f -P %s' % fname)
+ loop = out.strip()
+ fatpart = '%sp1' % loop
+ u_boot_utils.run_and_log(cons, 'sudo mkfs.vfat %s' % fatpart)
+ u_boot_utils.run_and_log(
+ cons, 'sudo mount -o loop %s %s -o uid=%d,gid=%d' %
+ (fatpart, mnt, os.getuid(), os.getgid()))
+ mounted = True
+
+ vmlinux = 'vmlinuz-5.3.7-301.fc31.armv7hl'
+ initrd = 'initramfs-5.3.7-301.fc31.armv7hl.img'
+ dtbdir = 'dtb-5.3.7-301.fc31.armv7hl'
+ script = '''# extlinux.conf generated by appliance-creator
+ui menu.c32
+menu autoboot Welcome to Fedora-Workstation-armhfp-31-1.9. Automatic boot in # second{,s}. Press a key for options.
+menu title Fedora-Workstation-armhfp-31-1.9 Boot Options.
+menu hidden
+timeout 20
+totaltimeout 600
+
+label Fedora-Workstation-armhfp-31-1.9 (5.3.7-301.fc31.armv7hl)
+ kernel /%s
+ append ro root=UUID=9732b35b-4cd5-458b-9b91-80f7047e0b8a rhgb quiet LANG=en_US.UTF-8 cma=192MB cma=256MB
+ fdtdir /%s/
+ initrd /%s''' % (vmlinux, dtbdir, initrd)
+ ext = os.path.join(mnt, 'extlinux')
+ mkdir_cond(ext)
+
+ with open(os.path.join(ext, 'extlinux.conf'), 'w') as fd:
+ print(script, file=fd)
+
+ inf = os.path.join(cons.config.persistent_data_dir, 'inf')
+ with open(inf, 'wb') as fd:
+ fd.write(gzip.compress(b'vmlinux'))
+ u_boot_utils.run_and_log(cons, 'mkimage -f auto -d %s %s' %
+ (inf, os.path.join(mnt, vmlinux)))
+
+ with open(os.path.join(mnt, initrd), 'w') as fd:
+ print('initrd', file=fd)
+
+ mkdir_cond(os.path.join(mnt, dtbdir))
+
+ dtb_file = os.path.join(mnt, '%s/sandbox.dtb' % dtbdir)
+ u_boot_utils.run_and_log(
+ cons, 'dtc -o %s' % dtb_file, stdin=b'/dts-v1/; / {};')
+ complete = True
+ except ValueError as exc:
+ print('Falled to create image, failing back to prepared copy: %s',
+ str(exc))
+ finally:
+ if mounted:
+ u_boot_utils.run_and_log(cons, 'sudo umount %s' % mnt)
+ if loop:
+ u_boot_utils.run_and_log(cons, 'sudo losetup -d %s' % loop)
+
+ if not complete:
+ # Use a prepared image since we cannot create one
+ infname = os.path.join(cons.config.source_dir,
+ 'test/py/tests/bootstd/mmc1.img.xz')
+ u_boot_utils.run_and_log(
+ cons,
+ ['sh', '-c', 'xz -dc %s >%s' % (infname, fname)])
+
+
@pytest.mark.buildconfigspec('ut_dm')
def test_ut_dm_init(u_boot_console):
"""Initialize data for ut dm tests."""
@@ -21,6 +114,16 @@ def test_ut_dm_init(u_boot_console):
with open(fn, 'wb') as fh:
fh.write(data)
+@pytest.mark.buildconfigspec('cmd_bootflow')
+def test_ut_dm_init_bootstd(u_boot_console):
+ """Initialise data for bootflow tests"""
+
+ setup_bootflow_image(u_boot_console)
+
+ # Restart so that the new mmc1.img is picked up
+ u_boot_console.restart_uboot()
+
+
def test_ut(u_boot_console, ut_subtest):
"""Execute a "ut" subtest.
diff --git a/test/str_ut.c b/test/str_ut.c
index d2840d5152..5a844347c2 100644
--- a/test/str_ut.c
+++ b/test/str_ut.c
@@ -202,6 +202,78 @@ static int str_dectoul(struct unit_test_state *uts)
}
STR_TEST(str_dectoul, 0);
+static int str_itoa(struct unit_test_state *uts)
+{
+ ut_asserteq_str("123", simple_itoa(123));
+ ut_asserteq_str("0", simple_itoa(0));
+ ut_asserteq_str("2147483647", simple_itoa(0x7fffffff));
+ ut_asserteq_str("4294967295", simple_itoa(0xffffffff));
+
+ /* Use #ifdef here to avoid a compiler warning on 32-bit machines */
+#ifdef CONFIG_PHYS_64BIT
+ if (sizeof(ulong) == 8) {
+ ut_asserteq_str("9223372036854775807",
+ simple_itoa((1UL << 63) - 1));
+ ut_asserteq_str("18446744073709551615", simple_itoa(-1));
+ }
+#endif /* CONFIG_PHYS_64BIT */
+
+ return 0;
+}
+STR_TEST(str_itoa, 0);
+
+static int str_xtoa(struct unit_test_state *uts)
+{
+ ut_asserteq_str("7f", simple_xtoa(127));
+ ut_asserteq_str("00", simple_xtoa(0));
+ ut_asserteq_str("7fffffff", simple_xtoa(0x7fffffff));
+ ut_asserteq_str("ffffffff", simple_xtoa(0xffffffff));
+
+ /* Use #ifdef here to avoid a compiler warning on 32-bit machines */
+#ifdef CONFIG_PHYS_64BIT
+ if (sizeof(ulong) == 8) {
+ ut_asserteq_str("7fffffffffffffff",
+ simple_xtoa((1UL << 63) - 1));
+ ut_asserteq_str("ffffffffffffffff", simple_xtoa(-1));
+ }
+#endif /* CONFIG_PHYS_64BIT */
+
+ return 0;
+}
+STR_TEST(str_xtoa, 0);
+
+static int str_trailing(struct unit_test_state *uts)
+{
+ const char str1[] = "abc123def";
+ const char str2[] = "abc123def456";
+ const char *end;
+
+ ut_asserteq(-1, trailing_strtol(""));
+ ut_asserteq(-1, trailing_strtol("123"));
+ ut_asserteq(123, trailing_strtol("abc123"));
+ ut_asserteq(4, trailing_strtol("12c4"));
+ ut_asserteq(-1, trailing_strtol("abd"));
+ ut_asserteq(-1, trailing_strtol("abc123def"));
+
+ ut_asserteq(-1, trailing_strtoln(str1, NULL));
+ ut_asserteq(123, trailing_strtoln(str1, str1 + 6));
+ ut_asserteq(-1, trailing_strtoln(str1, str1 + 9));
+
+ ut_asserteq(3, trailing_strtol("a3"));
+
+ ut_asserteq(123, trailing_strtoln_end(str1, str1 + 6, &end));
+ ut_asserteq(3, end - str1);
+
+ ut_asserteq(-1, trailing_strtoln_end(str1, str1 + 7, &end));
+ ut_asserteq(7, end - str1);
+
+ ut_asserteq(456, trailing_strtoln_end(str2, NULL, &end));
+ ut_asserteq(9, end - str2);
+
+ return 0;
+}
+STR_TEST(str_trailing, 0);
+
int do_ut_str(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
{
struct unit_test *tests = UNIT_TEST_SUITE_START(str_test);