summaryrefslogtreecommitdiff
path: root/boot
diff options
context:
space:
mode:
authorTom Rini <[email protected]>2022-08-12 12:51:14 -0400
committerTom Rini <[email protected]>2022-08-12 12:51:14 -0400
commit6fc212779c990ff27a430e370bfb8fac01ddde7f (patch)
tree32ecaafa1d653e275683cfacac41dd2bb57efca1 /boot
parentf5003e0791dbe796bf7b41515d67ae5527679ec9 (diff)
parent5fe76d460d857b00d582d7cd6cea9ac740ea912b (diff)
Merge branch '2022-08-11-verified-boot-for-embedded-initial-support'
To quote Simon: This adds the concept of a VBE method to U-Boot, along with an implementation of the 'VBE simple' method, basically a simple way of updating firmware in MMC from userspace and monitoring it from U-Boot. VBE simple is implemented in fwupd. U-Boot's role is to set up the device tree with the required firmware-update properties and provide the developer with information about the current VBE state. To that end this series includes a new 'vbe' command that allows VBE methods to be listed and examined. As part of this work, support for doing FDT fixups via the event interface is provided, along with the ability to write to the device tree via the ofnode interface. Another (significant) change is that bootmeths now have a 'global' flag, to allow the implementation of EFI bootmgr (and VBE) to be cleaned up. The 'system' bootdev is no-longer needed and these bootmeths are scanned first. Further work is needed to pull everything together, but this is a step along the way.
Diffstat (limited to 'boot')
-rw-r--r--boot/Kconfig29
-rw-r--r--boot/Makefile5
-rw-r--r--boot/bootdev-uclass.c15
-rw-r--r--boot/bootflow.c63
-rw-r--r--boot/bootmeth-uclass.c105
-rw-r--r--boot/bootmeth_distro.c14
-rw-r--r--boot/bootmeth_efi_mgr.c35
-rw-r--r--boot/bootstd-uclass.c13
-rw-r--r--boot/image-fdt.c11
-rw-r--r--boot/system_bootdev.c66
-rw-r--r--boot/vbe.c119
-rw-r--r--boot/vbe_simple.c315
12 files changed, 666 insertions, 124 deletions
diff --git a/boot/Kconfig b/boot/Kconfig
index a294ad769ed..6b3b8f072cb 100644
--- a/boot/Kconfig
+++ b/boot/Kconfig
@@ -349,6 +349,13 @@ config BOOTSTD_BOOTCOMMAND
standard boot does not support all of the features of distro boot
yet.
+config BOOTMETH_GLOBAL
+ bool
+ help
+ Add support for global bootmeths. This feature is used by VBE and
+ EFI bootmgr, since they take full control over which bootdevs are
+ selected to boot.
+
config BOOTMETH_DISTRO
bool "Bootdev support for distro boot"
select PXE_UTILS
@@ -391,6 +398,28 @@ config BOOTMETH_EFILOADER
This provides a way to try out standard boot on an existing boot flow.
+config BOOTMETH_VBE
+ bool "Bootdev support for Verified Boot for Embedded"
+ depends on FIT
+ default y
+ select BOOTMETH_GLOBAL
+ help
+ Enables support for VBE boot. This is a standard boot method which
+ supports selection of various firmware components, seleciton of an OS to
+ boot as well as updating these using fwupd.
+
+if BOOTMETH_VBE
+
+config BOOTMETH_VBE_SIMPLE
+ bool "Bootdev support for VBE 'simple' method"
+ default y
+ help
+ Enables support for VBE 'simple' boot. This allows updating a single
+ firmware image in boot media such as MMC. It does not support any sort
+ of rollback, recovery or A/B boot.
+
+endif # BOOTMETH_VBE
+
config BOOTMETH_SANDBOX
def_bool y
depends on SANDBOX
diff --git a/boot/Makefile b/boot/Makefile
index 124065a03fa..67e335255f1 100644
--- a/boot/Makefile
+++ b/boot/Makefile
@@ -18,7 +18,7 @@ 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) += bootdev-uclass.o
obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootflow.o
obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootmeth-uclass.o
obj-$(CONFIG_$(SPL_TPL_)BOOTSTD) += bootstd-uclass.o
@@ -46,3 +46,6 @@ obj-$(CONFIG_CMD_ADTIMG) += image-android-dt.o
ifdef CONFIG_SPL_BUILD
obj-$(CONFIG_SPL_LOAD_FIT) += common_fit.o
endif
+
+obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE) += vbe.o
+obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE) += vbe_simple.o
diff --git a/boot/bootdev-uclass.c b/boot/bootdev-uclass.c
index 1ede933c2f2..13ac69eb392 100644
--- a/boot/bootdev-uclass.c
+++ b/boot/bootdev-uclass.c
@@ -36,7 +36,6 @@ enum {
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;
@@ -52,7 +51,11 @@ int bootdev_add_bootflow(struct bootflow *bflow)
memcpy(new, bflow, sizeof(*bflow));
list_add_tail(&new->glob_node, &std->glob_head);
- list_add_tail(&new->bm_node, &ucp->bootflow_head);
+ if (bflow->dev) {
+ struct bootdev_uc_plat *ucp = dev_get_uclass_plat(bflow->dev);
+
+ list_add_tail(&new->bm_node, &ucp->bootflow_head);
+ }
return 0;
}
@@ -604,14 +607,14 @@ int bootdev_setup_iter_order(struct bootflow_iter *iter, struct udevice **devp)
log_debug("Expected %d bootdevs, found %d using aliases\n",
count, upto);
- count = build_order(bootstd, order, upto);
- if (count < 0) {
+ ret = build_order(bootstd, order, upto);
+ if (ret < 0) {
free(order);
- return log_msg_ret("build", count);
+ return log_msg_ret("build", ret);
}
+ iter->num_devs = ret;
iter->dev_order = order;
- iter->num_devs = count;
iter->cur_dev = 0;
dev = *order;
diff --git a/boot/bootflow.c b/boot/bootflow.c
index 24ba3c34660..5d94a27ff84 100644
--- a/boot/bootflow.c
+++ b/boot/bootflow.c
@@ -86,6 +86,7 @@ int bootflow_next_glob(struct bootflow **bflowp)
void bootflow_iter_init(struct bootflow_iter *iter, int flags)
{
memset(iter, '\0', sizeof(*iter));
+ iter->first_glob_method = -1;
iter->flags = flags;
}
@@ -115,11 +116,17 @@ int bootflow_iter_drop_bootmeth(struct bootflow_iter *iter,
static void bootflow_iter_set_dev(struct bootflow_iter *iter,
struct udevice *dev)
{
+ struct bootmeth_uc_plat *ucp = dev_get_uclass_plat(iter->method);
+
iter->dev = dev;
if ((iter->flags & (BOOTFLOWF_SHOW | BOOTFLOWF_SINGLE_DEV)) ==
BOOTFLOWF_SHOW) {
if (dev)
printf("Scanning bootdev '%s':\n", dev->name);
+ else if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) &&
+ ucp->flags & BOOTMETHF_GLOBAL)
+ printf("Scanning global bootmeth '%s':\n",
+ iter->method->name);
else
printf("No more bootdevs\n");
}
@@ -133,8 +140,12 @@ static void bootflow_iter_set_dev(struct bootflow_iter *iter,
static int iter_incr(struct bootflow_iter *iter)
{
struct udevice *dev;
+ bool inc_dev = true;
+ bool global;
int ret;
+ global = iter->doing_global;
+
if (iter->err == BF_NO_MORE_DEVICES)
return BF_NO_MORE_DEVICES;
@@ -144,6 +155,21 @@ static int iter_incr(struct bootflow_iter *iter)
iter->method = iter->method_order[iter->cur_method];
return 0;
}
+
+ /*
+ * If we have finished scanning the global bootmeths, start the
+ * normal bootdev scan
+ */
+ if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) && global) {
+ iter->num_methods = iter->first_glob_method;
+ iter->doing_global = false;
+
+ /*
+ * Don't move to the next dev as we haven't tried this
+ * one yet!
+ */
+ inc_dev = false;
+ }
}
/* No more bootmeths; start at the first one, and... */
@@ -169,14 +195,18 @@ static int iter_incr(struct bootflow_iter *iter)
/* ...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 (inc_dev)
+ iter->cur_dev++;
+ 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 */
@@ -199,6 +229,15 @@ static int bootflow_check(struct bootflow_iter *iter, struct bootflow *bflow)
struct udevice *dev;
int ret;
+ if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) && iter->doing_global) {
+ bootflow_iter_set_dev(iter, NULL);
+ ret = bootmeth_get_bootflow(iter->method, bflow);
+ if (ret)
+ return log_msg_ret("glob", ret);
+
+ return 0;
+ }
+
dev = iter->dev;
ret = bootdev_get_bootflow(dev, iter, bflow);
@@ -231,19 +270,22 @@ int bootflow_scan_bootdev(struct udevice *dev, struct bootflow_iter *iter,
{
int ret;
+ if (dev)
+ flags |= BOOTFLOWF_SKIP_GLOBAL;
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);
+ ret = bootmeth_setup_iter_order(iter, !(flags & BOOTFLOWF_SKIP_GLOBAL));
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];
+ if (!IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) || !iter->doing_global)
+ bootflow_iter_set_dev(iter, dev);
ret = bootflow_check(iter, bflow);
if (ret) {
@@ -307,7 +349,8 @@ void bootflow_free(struct bootflow *bflow)
void bootflow_remove(struct bootflow *bflow)
{
- list_del(&bflow->bm_node);
+ if (bflow->dev)
+ list_del(&bflow->bm_node);
list_del(&bflow->glob_node);
bootflow_free(bflow);
diff --git a/boot/bootmeth-uclass.c b/boot/bootmeth-uclass.c
index c040d5f92b2..2d7652edeab 100644
--- a/boot/bootmeth-uclass.c
+++ b/boot/bootmeth-uclass.c
@@ -20,6 +20,16 @@
DECLARE_GLOBAL_DATA_PTR;
+int bootmeth_get_state_desc(struct udevice *dev, char *buf, int maxsize)
+{
+ const struct bootmeth_ops *ops = bootmeth_get_ops(dev);
+
+ if (!ops->get_state_desc)
+ return -ENOSYS;
+
+ return ops->get_state_desc(dev, buf, maxsize);
+}
+
int bootmeth_check(struct udevice *dev, struct bootflow_iter *iter)
{
const struct bootmeth_ops *ops = bootmeth_get_ops(dev);
@@ -61,18 +71,21 @@ int bootmeth_read_file(struct udevice *dev, struct bootflow *bflow,
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)
+int bootmeth_get_bootflow(struct udevice *dev, struct bootflow *bflow)
+{
+ const struct bootmeth_ops *ops = bootmeth_get_ops(dev);
+
+ if (!ops->read_bootflow)
+ return -ENOSYS;
+ memset(bflow, '\0', sizeof(*bflow));
+ bflow->dev = NULL;
+ bflow->method = dev;
+ bflow->state = BOOTFLOWST_BASE;
+
+ return ops->read_bootflow(dev, bflow);
+}
+
+int bootmeth_setup_iter_order(struct bootflow_iter *iter, bool include_global)
{
struct bootstd_priv *std;
struct udevice **order;
@@ -95,29 +108,77 @@ int bootmeth_setup_iter_order(struct bootflow_iter *iter)
/* If we have an ordering, copy it */
if (IS_ENABLED(CONFIG_BOOTSTD_FULL) && std->bootmeth_count) {
+ int i;
+
+ /*
+ * We don't support skipping global bootmeths. Instead, the user
+ * should omit them from the ordering
+ */
+ if (!include_global)
+ return log_msg_ret("glob", -EPERM);
memcpy(order, std->bootmeth_order,
count * sizeof(struct bootmeth *));
+
+ if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL)) {
+ for (i = 0; i < count; i++) {
+ struct udevice *dev = order[i];
+ struct bootmeth_uc_plat *ucp;
+ bool is_global;
+
+ ucp = dev_get_uclass_plat(dev);
+ is_global = ucp->flags &
+ BOOTMETHF_GLOBAL;
+ if (is_global) {
+ iter->first_glob_method = i;
+ break;
+ }
+ }
+ }
} else {
struct udevice *dev;
- int i, upto;
+ int i, upto, pass;
/*
- * 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.
+ * Do two passes, one to find the normal bootmeths and another
+ * to find the global ones, if required, The global ones go at
+ * the end.
*/
- 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;
+ for (pass = 0, upto = 0; pass < 1 + include_global; pass++) {
+ if (pass)
+ iter->first_glob_method = 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 < count && i < 20 + count * 2; i++) {
+ struct bootmeth_uc_plat *ucp;
+ bool is_global;
+
+ ret = uclass_get_device_by_seq(UCLASS_BOOTMETH,
+ i, &dev);
+ if (ret)
+ continue;
+ ucp = dev_get_uclass_plat(dev);
+ is_global =
+ IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) &&
+ (ucp->flags & BOOTMETHF_GLOBAL);
+ if (pass ? is_global : !is_global)
+ order[upto++] = dev;
+ }
}
count = upto;
}
+ if (!count)
+ return log_msg_ret("count2", -ENOENT);
+ if (IS_ENABLED(CONFIG_BOOTMETH_GLOBAL) && include_global &&
+ iter->first_glob_method != -1 && iter->first_glob_method != count) {
+ iter->cur_method = iter->first_glob_method;
+ iter->doing_global = true;
+ }
iter->method_order = order;
iter->num_methods = count;
- iter->cur_method = 0;
return 0;
}
diff --git a/boot/bootmeth_distro.c b/boot/bootmeth_distro.c
index 2b41e654ade..fea09b2c2fb 100644
--- a/boot/bootmeth_distro.c
+++ b/boot/bootmeth_distro.c
@@ -22,6 +22,19 @@
#include <mmc.h>
#include <pxe_utils.h>
+static int distro_get_state_desc(struct udevice *dev, char *buf, int maxsize)
+{
+ if (IS_ENABLED(CONFIG_SANDBOX)) {
+ int len;
+
+ len = snprintf(buf, maxsize, "OK");
+
+ return len + 1 < maxsize ? 0 : -ENOSPC;
+ }
+
+ return 0;
+}
+
static int disto_getfile(struct pxe_context *ctx, const char *file_path,
char *file_addr, ulong *sizep)
{
@@ -123,6 +136,7 @@ static int distro_bootmeth_bind(struct udevice *dev)
}
static struct bootmeth_ops distro_bootmeth_ops = {
+ .get_state_desc = distro_get_state_desc,
.check = distro_check,
.read_bootflow = distro_read_bootflow,
.read_file = bootmeth_common_read_file,
diff --git a/boot/bootmeth_efi_mgr.c b/boot/bootmeth_efi_mgr.c
index a6914466db7..2f327c1f8ce 100644
--- a/boot/bootmeth_efi_mgr.c
+++ b/boot/bootmeth_efi_mgr.c
@@ -15,6 +15,22 @@
#include <command.h>
#include <dm.h>
+/**
+ * struct efi_mgr_priv - private info for the efi-mgr driver
+ *
+ * @fake_bootflow: Fake a valid bootflow for testing
+ */
+struct efi_mgr_priv {
+ bool fake_dev;
+};
+
+void sandbox_set_fake_efi_mgr_dev(struct udevice *dev, bool fake_dev)
+{
+ struct efi_mgr_priv *priv = dev_get_priv(dev);
+
+ priv->fake_dev = fake_dev;
+}
+
static int efi_mgr_check(struct udevice *dev, struct bootflow_iter *iter)
{
int ret;
@@ -29,13 +45,16 @@ static int efi_mgr_check(struct udevice *dev, struct bootflow_iter *iter)
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;
+ struct efi_mgr_priv *priv = dev_get_priv(dev);
- return 0;
+ if (priv->fake_dev) {
+ bflow->state = BOOTFLOWST_READY;
+ return 0;
+ }
+
+ /* To be implemented */
+
+ return -EINVAL;
}
static int efi_mgr_read_file(struct udevice *dev, struct bootflow *bflow,
@@ -61,6 +80,7 @@ static int bootmeth_efi_mgr_bind(struct udevice *dev)
struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev);
plat->desc = "EFI bootmgr flow";
+ plat->flags = BOOTMETHF_GLOBAL;
return 0;
}
@@ -77,10 +97,11 @@ static const struct udevice_id efi_mgr_bootmeth_ids[] = {
{ }
};
-U_BOOT_DRIVER(bootmeth_zefi_mgr) = {
+U_BOOT_DRIVER(bootmeth_efi_mgr) = {
.name = "bootmeth_efi_mgr",
.id = UCLASS_BOOTMETH,
.of_match = efi_mgr_bootmeth_ids,
.ops = &efi_mgr_bootmeth_ops,
.bind = bootmeth_efi_mgr_bind,
+ .priv_auto = sizeof(struct efi_mgr_priv),
};
diff --git a/boot/bootstd-uclass.c b/boot/bootstd-uclass.c
index 3c6c32ae604..565c22a36e7 100644
--- a/boot/bootstd-uclass.c
+++ b/boot/bootstd-uclass.c
@@ -133,12 +133,7 @@ int dm_scan_other(bool pre_reloc_only)
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)) {
+ if (drv->id == UCLASS_BOOTMETH) {
const char *name = drv->name;
if (!strncmp("bootmeth_", name, 9))
@@ -150,12 +145,6 @@ int dm_scan_other(bool pre_reloc_only)
}
}
- /* 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;
}
diff --git a/boot/image-fdt.c b/boot/image-fdt.c
index 9db2cee9942..5e5b24674d3 100644
--- a/boot/image-fdt.c
+++ b/boot/image-fdt.c
@@ -21,6 +21,7 @@
#include <linux/libfdt.h>
#include <mapmem.h>
#include <asm/io.h>
+#include <dm/ofnode.h>
#include <tee/optee.h>
#ifndef CONFIG_SYS_FDT_PAD
@@ -668,6 +669,16 @@ int image_setup_libfdt(bootm_headers_t *images, void *blob,
goto err;
}
}
+ if (CONFIG_IS_ENABLED(EVENT)) {
+ struct event_ft_fixup fixup;
+
+ fixup.tree = oftree_default();
+ ret = event_notify(EVT_FT_FIXUP, &fixup, sizeof(fixup));
+ if (ret) {
+ printf("ERROR: fdt fixup event failed: %d\n", ret);
+ goto err;
+ }
+ }
/* Delete the old LMB reservation */
if (lmb)
diff --git a/boot/system_bootdev.c b/boot/system_bootdev.c
deleted file mode 100644
index 432d2034780..00000000000
--- a/boot/system_bootdev.c
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Bootdevice for system, used for bootmeths not tied to any partition device
- *
- * Copyright 2021 Google LLC
- * Written by Simon Glass <[email protected]>
- */
-
-#define LOG_CATEGORY UCLASS_BOOTSTD
-
-#include <common.h>
-#include <bootdev.h>
-#include <bootflow.h>
-#include <bootmeth.h>
-#include <command.h>
-#include <distro.h>
-#include <dm.h>
-#include <log.h>
-#include <net.h>
-
-static int system_get_bootflow(struct udevice *dev, struct bootflow_iter *iter,
- struct bootflow *bflow)
-{
- int ret;
-
- /* Must be an bootstd device */
- ret = bootflow_iter_uses_system(iter);
- if (ret)
- return log_msg_ret("net", ret);
-
- ret = bootmeth_check(bflow->method, iter);
- if (ret)
- return log_msg_ret("check", ret);
-
- ret = bootmeth_read_bootflow(bflow->method, bflow);
- if (ret)
- return log_msg_ret("method", ret);
-
- return 0;
-}
-
-static int system_bootdev_bind(struct udevice *dev)
-{
- struct bootdev_uc_plat *ucp = dev_get_uclass_plat(dev);
-
- ucp->prio = BOOTDEVP_6_SYSTEM;
-
- return 0;
-}
-
-struct bootdev_ops system_bootdev_ops = {
- .get_bootflow = system_get_bootflow,
-};
-
-static const struct udevice_id system_bootdev_ids[] = {
- { .compatible = "u-boot,bootdev-system" },
- { }
-};
-
-U_BOOT_DRIVER(system_bootdev) = {
- .name = "system_bootdev",
- .id = UCLASS_BOOTDEV,
- .ops = &system_bootdev_ops,
- .bind = system_bootdev_bind,
- .of_match = system_bootdev_ids,
-};
diff --git a/boot/vbe.c b/boot/vbe.c
new file mode 100644
index 00000000000..e6ee087dc24
--- /dev/null
+++ b/boot/vbe.c
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Verified Boot for Embedded (VBE) access functions
+ *
+ * Copyright 2022 Google LLC
+ * Written by Simon Glass <[email protected]>
+ */
+
+#include <common.h>
+#include <bootmeth.h>
+#include <bootstd.h>
+#include <dm.h>
+#include <image.h>
+#include <vbe.h>
+#include <dm/uclass-internal.h>
+
+/**
+ * is_vbe() - Check if a device is a VBE method
+ *
+ * @dev: Device to check
+ * @return true if this is a VBE bootmth device, else false
+ */
+static bool is_vbe(struct udevice *dev)
+{
+ return !strncmp("vbe", dev->driver->name, 3);
+}
+
+int vbe_find_next_device(struct udevice **devp)
+{
+ for (uclass_find_next_device(devp);
+ *devp;
+ uclass_find_next_device(devp)) {
+ if (is_vbe(*devp))
+ return 0;
+ }
+
+ return 0;
+}
+
+int vbe_find_first_device(struct udevice **devp)
+{
+ uclass_find_first_device(UCLASS_BOOTMETH, devp);
+ if (*devp && is_vbe(*devp))
+ return 0;
+
+ return vbe_find_next_device(devp);
+}
+
+int vbe_list(void)
+{
+ struct bootstd_priv *std;
+ struct udevice *dev;
+ int ret;
+
+ ret = bootstd_get_priv(&std);
+ if (ret)
+ return ret;
+
+ printf("%3s %-3s %-15s %-15s %s\n", "#", "Sel", "Device", "Driver",
+ "Description");
+ printf("%3s %-3s %-15s %-15s %s\n", "---", "---", "--------------",
+ "--------------", "-----------");
+ for (ret = vbe_find_first_device(&dev); dev;
+ ret = vbe_find_next_device(&dev)) {
+ const struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev);
+
+ printf("%3d %-3s %-15s %-15s %s\n", dev_seq(dev),
+ std->vbe_bootmeth == dev ? "*" : "", dev->name,
+ dev->driver->name, plat->desc);
+ }
+ printf("%3s %-3s %-15s %-15s %s\n", "---", "---", "--------------",
+ "--------------", "-----------");
+
+ return 0;
+}
+
+int vbe_select(struct udevice *dev)
+{
+ struct bootstd_priv *std;
+ int ret;
+
+ ret = bootstd_get_priv(&std);
+ if (ret)
+ return ret;
+ std->vbe_bootmeth = dev;
+
+ return 0;
+}
+
+int vbe_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 */
+ if (*endp) {
+ ret = uclass_get_device_by_name(UCLASS_BOOTMETH, name, &dev);
+ if (ret) {
+ printf("Cannot probe VBE bootmeth '%s' (err=%d)\n", name,
+ ret);
+ return ret;
+ }
+
+ /* select by number */
+ } else {
+ ret = uclass_get_device_by_seq(UCLASS_BOOTMETH, seq, &dev);
+ if (ret) {
+ printf("Cannot find '%s' (err=%d)\n", name, ret);
+ return ret;
+ }
+ }
+
+ *devp = dev;
+
+ return 0;
+}
diff --git a/boot/vbe_simple.c b/boot/vbe_simple.c
new file mode 100644
index 00000000000..a395bc20a60
--- /dev/null
+++ b/boot/vbe_simple.c
@@ -0,0 +1,315 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Verified Boot for Embedded (VBE) 'simple' method
+ *
+ * Copyright 2022 Google LLC
+ * Written by Simon Glass <[email protected]>
+ */
+
+#include <common.h>
+#include <log.h>
+#include <memalign.h>
+#include <part.h>
+#include <bootflow.h>
+#include <bootmeth.h>
+#include <dm.h>
+#include <mmc.h>
+#include <vbe.h>
+#include <version_string.h>
+#include <dm/device-internal.h>
+#include <dm/ofnode.h>
+#include <u-boot/crc.h>
+
+enum {
+ MAX_VERSION_LEN = 256,
+
+ NVD_HDR_VER_SHIFT = 0,
+ NVD_HDR_VER_MASK = 0xf,
+ NVD_HDR_SIZE_SHIFT = 4,
+ NVD_HDR_SIZE_MASK = 0xf << NVD_HDR_SIZE_SHIFT,
+
+ /* Firmware key-version is in the top 16 bits of fw_ver */
+ FWVER_KEY_SHIFT = 16,
+ FWVER_FW_MASK = 0xffff,
+
+ NVD_HDR_VER_CUR = 1, /* current version */
+};
+
+/** struct simple_priv - information read from the device tree */
+struct simple_priv {
+ u32 area_start;
+ u32 area_size;
+ u32 skip_offset;
+ u32 state_offset;
+ u32 state_size;
+ u32 version_offset;
+ u32 version_size;
+ const char *storage;
+};
+
+/** struct simple_state - state information read from media
+ *
+ * @fw_version: Firmware version string
+ * @fw_vernum: Firmware version number
+ */
+struct simple_state {
+ char fw_version[MAX_VERSION_LEN];
+ u32 fw_vernum;
+};
+
+/** struct simple_nvdata - storage format for non-volatile data */
+struct simple_nvdata {
+ u8 crc8;
+ u8 hdr;
+ u16 spare1;
+ u32 fw_vernum;
+ u8 spare2[0x38];
+};
+
+static int simple_read_version(struct udevice *dev, struct blk_desc *desc,
+ u8 *buf, struct simple_state *state)
+{
+ struct simple_priv *priv = dev_get_priv(dev);
+ int start;
+
+ if (priv->version_size > MMC_MAX_BLOCK_LEN)
+ return log_msg_ret("ver", -E2BIG);
+
+ start = priv->area_start + priv->version_offset;
+ if (start & (MMC_MAX_BLOCK_LEN - 1))
+ return log_msg_ret("get", -EBADF);
+ start /= MMC_MAX_BLOCK_LEN;
+
+ if (blk_dread(desc, start, 1, buf) != 1)
+ return log_msg_ret("read", -EIO);
+ strlcpy(state->fw_version, buf, MAX_VERSION_LEN);
+ log_debug("version=%s\n", state->fw_version);
+
+ return 0;
+}
+
+static int simple_read_nvdata(struct udevice *dev, struct blk_desc *desc,
+ u8 *buf, struct simple_state *state)
+{
+ struct simple_priv *priv = dev_get_priv(dev);
+ uint hdr_ver, hdr_size, size, crc;
+ const struct simple_nvdata *nvd;
+ int start;
+
+ if (priv->state_size > MMC_MAX_BLOCK_LEN)
+ return log_msg_ret("state", -E2BIG);
+
+ start = priv->area_start + priv->state_offset;
+ if (start & (MMC_MAX_BLOCK_LEN - 1))
+ return log_msg_ret("get", -EBADF);
+ start /= MMC_MAX_BLOCK_LEN;
+
+ if (blk_dread(desc, start, 1, buf) != 1)
+ return log_msg_ret("read", -EIO);
+ nvd = (struct simple_nvdata *)buf;
+ hdr_ver = (nvd->hdr & NVD_HDR_VER_MASK) >> NVD_HDR_VER_SHIFT;
+ hdr_size = (nvd->hdr & NVD_HDR_SIZE_MASK) >> NVD_HDR_SIZE_SHIFT;
+ if (hdr_ver != NVD_HDR_VER_CUR)
+ return log_msg_ret("hdr", -EPERM);
+ size = 1 << hdr_size;
+ if (size > sizeof(*nvd))
+ return log_msg_ret("sz", -ENOEXEC);
+
+ crc = crc8(0, buf + 1, size - 1);
+ if (crc != nvd->crc8)
+ return log_msg_ret("crc", -EPERM);
+ state->fw_vernum = nvd->fw_vernum;
+
+ log_debug("version=%s\n", state->fw_version);
+
+ return 0;
+}
+
+static int simple_read_state(struct udevice *dev, struct simple_state *state)
+{
+ ALLOC_CACHE_ALIGN_BUFFER(u8, buf, MMC_MAX_BLOCK_LEN);
+ struct simple_priv *priv = dev_get_priv(dev);
+ struct blk_desc *desc;
+ char devname[16];
+ const char *end;
+ int devnum;
+ int ret;
+
+ /* First figure out the block device */
+ log_debug("storage=%s\n", priv->storage);
+ devnum = trailing_strtoln_end(priv->storage, NULL, &end);
+ if (devnum == -1)
+ return log_msg_ret("num", -ENODEV);
+ if (end - priv->storage >= sizeof(devname))
+ return log_msg_ret("end", -E2BIG);
+ strlcpy(devname, priv->storage, end - priv->storage + 1);
+ log_debug("dev=%s, %x\n", devname, devnum);
+
+ desc = blk_get_dev(devname, devnum);
+ if (!desc)
+ return log_msg_ret("get", -ENXIO);
+
+ ret = simple_read_version(dev, desc, buf, state);
+ if (ret)
+ return log_msg_ret("ver", ret);
+
+ ret = simple_read_nvdata(dev, desc, buf, state);
+ if (ret)
+ return log_msg_ret("nvd", ret);
+
+ return 0;
+}
+
+static int vbe_simple_get_state_desc(struct udevice *dev, char *buf,
+ int maxsize)
+{
+ struct simple_state state;
+ int ret;
+
+ ret = simple_read_state(dev, &state);
+ if (ret)
+ return log_msg_ret("read", ret);
+
+ if (maxsize < 30)
+ return -ENOSPC;
+ snprintf(buf, maxsize, "Version: %s\nVernum: %x/%x", state.fw_version,
+ state.fw_vernum >> FWVER_KEY_SHIFT,
+ state.fw_vernum & FWVER_FW_MASK);
+
+ return 0;
+}
+
+static int vbe_simple_read_bootflow(struct udevice *dev, struct bootflow *bflow)
+{
+ /* To be implemented */
+
+ return -EINVAL;
+}
+
+static struct bootmeth_ops bootmeth_vbe_simple_ops = {
+ .get_state_desc = vbe_simple_get_state_desc,
+ .read_bootflow = vbe_simple_read_bootflow,
+ .read_file = bootmeth_common_read_file,
+};
+
+int vbe_simple_fixup_node(ofnode node, struct simple_state *state)
+{
+ char *version;
+ int ret;
+
+ version = strdup(state->fw_version);
+ if (!version)
+ return log_msg_ret("ver", -ENOMEM);
+
+ ret = ofnode_write_string(node, "cur-version", version);
+ if (ret)
+ return log_msg_ret("ver", ret);
+ ret = ofnode_write_u32(node, "cur-vernum", state->fw_vernum);
+ if (ret)
+ return log_msg_ret("ver", ret);
+ ret = ofnode_write_string(node, "bootloader-version", version_string);
+ if (ret)
+ return log_msg_ret("fix", ret);
+
+ return 0;
+}
+
+/**
+ * bootmeth_vbe_simple_ft_fixup() - Write out all VBE simple data to the DT
+ *
+ * @ctx: Context for event
+ * @event: Event to process
+ * @return 0 if OK, -ve on error
+ */
+static int bootmeth_vbe_simple_ft_fixup(void *ctx, struct event *event)
+{
+ oftree tree = event->data.ft_fixup.tree;
+ struct udevice *dev;
+ ofnode node;
+ int ret;
+
+ /*
+ * Ideally we would have driver model support for fixups, but that does
+ * not exist yet. It is a step too far to try to do this before VBE is
+ * in place.
+ */
+ for (ret = vbe_find_first_device(&dev); dev;
+ ret = vbe_find_next_device(&dev)) {
+ struct simple_state state;
+
+ if (strcmp("vbe_simple", dev->driver->name))
+ continue;
+
+ /* Check if there is a node to fix up */
+ node = ofnode_path_root(tree, "/chosen/fwupd");
+ if (!ofnode_valid(node))
+ continue;
+ node = ofnode_find_subnode(node, dev->name);
+ if (!ofnode_valid(node))
+ continue;
+
+ log_debug("Fixing up: %s\n", dev->name);
+ ret = device_probe(dev);
+ if (ret)
+ return log_msg_ret("probe", ret);
+ ret = simple_read_state(dev, &state);
+ if (ret)
+ return log_msg_ret("read", ret);
+
+ ret = vbe_simple_fixup_node(node, &state);
+ if (ret)
+ return log_msg_ret("fix", ret);
+ }
+
+ return 0;
+}
+EVENT_SPY(EVT_FT_FIXUP, bootmeth_vbe_simple_ft_fixup);
+
+static int bootmeth_vbe_simple_probe(struct udevice *dev)
+{
+ struct simple_priv *priv = dev_get_priv(dev);
+
+ memset(priv, '\0', sizeof(*priv));
+ if (dev_read_u32(dev, "area-start", &priv->area_start) ||
+ dev_read_u32(dev, "area-size", &priv->area_size) ||
+ dev_read_u32(dev, "version-offset", &priv->version_offset) ||
+ dev_read_u32(dev, "version-size", &priv->version_size) ||
+ dev_read_u32(dev, "state-offset", &priv->state_offset) ||
+ dev_read_u32(dev, "state-size", &priv->state_size))
+ return log_msg_ret("read", -EINVAL);
+ dev_read_u32(dev, "skip-offset", &priv->skip_offset);
+ priv->storage = strdup(dev_read_string(dev, "storage"));
+ if (!priv->storage)
+ return log_msg_ret("str", -EINVAL);
+
+ return 0;
+}
+
+static int bootmeth_vbe_simple_bind(struct udevice *dev)
+{
+ struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev);
+
+ plat->desc = IS_ENABLED(CONFIG_BOOTSTD_FULL) ?
+ "VBE simple" : "vbe-simple";
+ plat->flags = BOOTMETHF_GLOBAL;
+
+ return 0;
+}
+
+#if CONFIG_IS_ENABLED(OF_REAL)
+static const struct udevice_id generic_simple_vbe_simple_ids[] = {
+ { .compatible = "fwupd,vbe-simple" },
+ { }
+};
+#endif
+
+U_BOOT_DRIVER(vbe_simple) = {
+ .name = "vbe_simple",
+ .id = UCLASS_BOOTMETH,
+ .of_match = of_match_ptr(generic_simple_vbe_simple_ids),
+ .ops = &bootmeth_vbe_simple_ops,
+ .bind = bootmeth_vbe_simple_bind,
+ .probe = bootmeth_vbe_simple_probe,
+ .flags = DM_FLAG_PRE_RELOC,
+ .priv_auto = sizeof(struct simple_priv),
+};